CPython ha un commento in Oggetti / typeobject.c su questo argomento:
Nelle versioni di CPython precedenti alla 3.5, il codice in
compatible_for_assignment
non era impostato per verificare correttamente la compatibilità del layout di memoria / slot / ecc. Per le classi non HEAPTYPE, quindi abbiamo semplicemente vietato l' __class__
assegnazione in ogni caso che non fosse HEAPTYPE -> HEAPTYPE.
Durante il ciclo di sviluppo 3.5, abbiamo corretto il codice
compatible_for_assignment
per verificare correttamente la compatibilità tra tipi arbitrari e abbiamo iniziato a consentire l' __class__
assegnazione in tutti i casi in cui i tipi vecchi e nuovi avevano effettivamente slot e layout di memoria compatibili (indipendentemente dal fatto che fossero implementati come HEAPTYPEs o no).
Poco prima del rilascio di 3.5, tuttavia, abbiamo scoperto che questo portava a problemi con tipi immutabili come int, in cui l'interprete assume che siano immutabili e integra alcuni valori. In precedenza questo non era un problema, perché erano davvero immutabili - in particolare, tutti i tipi in cui l'interprete applicava questo trucco di internamento erano stati allocati staticamente, quindi le vecchie regole di HEAPTYPE impedivano "accidentalmente" di consentire l' __class__
assegnazione. Ma con le modifiche __class__
all'assegnazione, abbiamo iniziato a consentire codice simile
class MyInt(int):
# ...
# Modifies the type of *all* instances of 1 in the whole program,
# including future instances (!), because the 1 object is interned.
(1).__class__ = MyInt
(vedi https://bugs.python.org/issue24912 ).
In teoria la soluzione corretta sarebbe quella di identificare quali classi si basano su questo invariante e in qualche modo non consentire l' __class__
assegnazione solo per loro, forse attraverso un meccanismo come un nuovo flag Py_TPFLAGS_IMMUTABLE (un approccio di "lista nera"). Ma in pratica, dal momento che questo problema non è stato notato alla fine del ciclo 3.5 RC, stiamo adottando l'approccio conservativo e ripristinando lo stesso controllo HEAPTYPE-> HEAPTYPE che avevamo prima, oltre a una "lista bianca". Per ora, la whitelist è composta solo dai sottotipi ModuleType, dal momento che questi sono i casi che hanno motivato la patch in primo luogo - vedi https://bugs.python.org/issue22986 - e poiché gli oggetti del modulo sono mutabili possiamo essere sicuri che sicuramente non vengono internati. Quindi ora consentiamo HEAPTYPE-> HEAPTYPE o
Sottotipo ModuleType -> Sottotipo ModuleType.
Per quanto ne sappiamo, tutto il codice oltre la seguente istruzione 'if' gestirà correttamente le classi non HEAPTYPE e il controllo HEAPTYPE è necessario solo per proteggere quel sottoinsieme di classi non HEAPTYPE per il quale l'interprete ha elaborato supponendo che tutte le istanze sono veramente immutabili.
Spiegazione:
CPython memorizza gli oggetti in due modi:
Gli oggetti sono strutture allocate sull'heap. Regole speciali si applicano all'uso di oggetti per garantire che siano adeguatamente raccolti. Gli oggetti non vengono mai allocati staticamente o in pila; è necessario accedervi solo tramite macro e funzioni speciali. (Gli oggetti di tipo sono eccezioni alla prima regola; i tipi standard sono rappresentati da oggetti di tipo inizializzati staticamente, sebbene il lavoro sull'unificazione di tipo / classe per Python 2.2 abbia reso possibile avere anche oggetti di tipo allocati in heap).
Informazioni tratte dal commento in Include / object.h .
Quando si tenta di impostare un nuovo valore su some_obj.__class__
, object_set_class
viene chiamata la funzione. È ereditato da PyBaseObject_Type , vedi /* tp_getset */
campo. Questa funzione verifica : il nuovo tipo può sostituire il vecchio tipo in some_obj
?
Prendi il tuo esempio:
class A:
pass
class B:
pass
o = object()
a = A()
b = B()
Primo caso:
a.__class__ = B
Il tipo di a
oggetto è A
, il tipo di heap, perché è allocato in modo dinamico. Così come il B
. Il a
tipo di è cambiato senza problemi.
Secondo caso:
o.__class__ = B
Il tipo di o
è il tipo incorporato object
( PyBaseObject_Type
). Non è un tipo di heap, quindi TypeError
viene sollevato:
TypeError: __class__ assignment only supported for heap types or ModuleType subclasses.