CPython tiene un comentario en Objects / typeobject.c sobre este tema:
En las versiones de CPython anteriores a 3.5, el código
compatible_for_assignment
no estaba configurado para verificar correctamente la compatibilidad del diseño de memoria / ranura / etc. para las clases que no son HEAPTYPE, por lo que simplemente rechazamos la __class__
asignación en cualquier caso que no fuera HEAPTYPE -> HEAPTYPE.
Durante el ciclo de desarrollo 3.5, arreglamos el código
compatible_for_assignment
para verificar correctamente la compatibilidad entre tipos arbitrarios, y comenzamos a permitir la __class__
asignación en todos los casos en que los tipos antiguos y nuevos tenían ranuras y diseño de memoria compatibles (independientemente de si se implementaron como HEAPTYPE) o no).
Sin embargo, justo antes de que se lanzara 3.5, descubrimos que esto condujo a problemas con tipos inmutables como int, donde el intérprete asume que son inmutables e interna algunos valores. Anteriormente, esto no era un problema, porque realmente eran inmutables, en particular, todos los tipos en los que el intérprete aplicaba este truco interno también estaban asignados estáticamente, por lo que las viejas reglas HEAPTYPE les impedían "accidentalmente" __class__
asignarlos. Pero con los cambios en la __class__
asignación, comenzamos a permitir códigos como
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
(ver https://bugs.python.org/issue24912 ).
En teoría, la solución adecuada sería identificar qué clases dependen de esta invariante y de alguna manera rechazar la __class__
asignación solo para ellas, tal vez a través de algún mecanismo como un nuevo indicador Py_TPFLAGS_IMMUTABLE (un enfoque de "lista negra"). Pero en la práctica, dado que este problema no se notó al final del ciclo 3.5 RC, estamos adoptando el enfoque conservador y restableciendo la misma verificación HEAPTYPE-> HEAPTYPE que teníamos, más una "lista blanca". Por ahora, la lista blanca consta solo de subtipos de ModuleType, ya que esos son los casos que motivaron el parche en primer lugar, consulte https://bugs.python.org/issue22986 , y dado que los objetos del módulo son mutables, podemos estar seguros que definitivamente no están siendo internados. Entonces ahora permitimos HEAPTYPE-> HEAPTYPE o
Subtipo ModuleType -> Subtipo ModuleType.
Hasta donde sabemos, todo el código más allá de la siguiente instrucción 'if' manejará correctamente las clases que no son HEAPTYPE, y la verificación HEAPTYPE es necesaria solo para proteger ese subconjunto de clases que no son HEAPTYPE para las cuales el intérprete ha asumido que Todas las instancias son verdaderamente inmutables.
Explicación:
CPython almacena objetos de dos maneras:
Los objetos son estructuras asignadas en el montón. Se aplican reglas especiales al uso de objetos para garantizar que se recojan correctamente. Los objetos nunca se asignan estáticamente o en la pila; se debe acceder a través de macros especiales y funciones solamente. (Los objetos de tipo son excepciones a la primera regla; los tipos estándar están representados por objetos de tipo inicializados estáticamente, aunque el trabajo en la unificación de tipo / clase para Python 2.2 también hizo posible tener objetos de tipo asignados en el montón).
Información del comentario en Incluir / objeto.h .
Cuando intenta establecer un nuevo valor some_obj.__class__
, object_set_class
se llama a la función. Se hereda de PyBaseObject_Type , ver /* tp_getset */
campo. Esta función comprueba : ¿puede el nuevo tipo reemplazar al antiguo some_obj
?
Toma tu ejemplo:
class A:
pass
class B:
pass
o = object()
a = A()
b = B()
Primer caso:
a.__class__ = B
El tipo de a
objeto es A
, el tipo de montón, porque se asigna dinámicamente. Así como el B
. El a
tipo de 's se cambia sin problema.
Segundo caso:
o.__class__ = B
El tipo de o
es el tipo incorporado object
( PyBaseObject_Type
). No es de tipo montón, por lo que TypeError
se genera:
TypeError: __class__ assignment only supported for heap types or ModuleType subclasses.