CPython a un commentaire dans Objects / typeobject.c sur ce sujet:
Dans les versions de CPython antérieures à 3.5, le code dans
compatible_for_assignment
n'était pas configuré pour vérifier correctement la compatibilité de la disposition de la mémoire / de l'emplacement / etc. pour les classes non HEAPTYPE, nous avons donc simplement interdit l' __class__
affectation dans tous les cas qui n'étaient pas HEAPTYPE -> HEAPTYPE.
Au cours du cycle de développement 3.5, nous avons corrigé le code
compatible_for_assignment
pour vérifier correctement la compatibilité entre les types arbitraires et commencé à autoriser l' __class__
attribution dans tous les cas où les anciens et les nouveaux types avaient en fait des emplacements et une disposition de mémoire compatibles (qu'ils soient ou non implémentés comme HEAPTYPE) ou pas).
Cependant, juste avant la sortie de la version 3.5, nous avons découvert que cela entraînait des problèmes avec des types immuables comme int, où l'interpréteur suppose qu'ils sont immuables et interne certaines valeurs. Auparavant, ce n'était pas un problème, car ils étaient vraiment immuables - en particulier, tous les types où l'interprète appliquait cette astuce d'internement se trouvaient également être alloués statiquement, de sorte que les anciennes règles HEAPTYPE les empêchaient "accidentellement" d'autoriser l' __class__
affectation. Mais avec les changements d' __class__
affectation, nous avons commencé à autoriser du code comme
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
(voir https://bugs.python.org/issue24912 ).
En théorie, la solution appropriée consisterait à identifier les classes qui s'appuient sur cet invariant et à interdire en quelque sorte l' __class__
affectation uniquement pour elles, peut-être via un mécanisme comme un nouveau drapeau Py_TPFLAGS_IMMUTABLE (une approche de "liste noire"). Mais en pratique, puisque ce problème n'a pas été remarqué à la fin du cycle 3.5 RC, nous adoptons l'approche conservatrice et rétablissons le même contrôle HEAPTYPE-> HEAPTYPE que nous avions, plus une "liste blanche". Pour l'instant, la liste blanche se compose uniquement de sous-types ModuleType, car ce sont les cas qui ont motivé le correctif en premier lieu - voir https://bugs.python.org/issue22986 - et puisque les objets de module sont mutables, nous pouvons être sûrs qu'ils ne sont définitivement pas internés. Alors maintenant, nous autorisons HEAPTYPE-> HEAPTYPE ou
Sous-type ModuleType -> Sous-type ModuleType.
Pour autant que nous le sachions, tout le code au-delà de l'instruction «if» suivante gérera correctement les classes non HEAPTYPE, et la vérification HEAPTYPE est nécessaire uniquement pour protéger ce sous-ensemble de classes non HEAPTYPE pour lequel l'interpréteur a cuit en supposant que toutes les instances sont vraiment immuables.
Explication:
CPython stocke les objets de deux manières:
Les objets sont des structures allouées sur le tas. Des règles spéciales s'appliquent à l'utilisation des objets pour garantir qu'ils sont correctement récupérés. Les objets ne sont jamais alloués statiquement ou sur la pile; ils doivent être accessibles uniquement via des macros et fonctions spéciales. (Les objets type sont des exceptions à la première règle; les types standard sont représentés par des objets type initialisés statiquement, bien que les travaux sur l'unification de type / classe pour Python 2.2 aient également permis d'avoir des objets type alloués par segment).
Informations du commentaire dans Include / object.h .
Lorsque vous essayez de définir une nouvelle valeur sur some_obj.__class__
, la object_set_class
fonction est appelée. Il est hérité de PyBaseObject_Type , voir /* tp_getset */
champ. Cette fonction vérifie : le nouveau type peut-il remplacer l'ancien type dans some_obj
?
Prenez votre exemple:
class A:
pass
class B:
pass
o = object()
a = A()
b = B()
Premier cas:
a.__class__ = B
Le type d' a
objet est A
le type de segment, car il est alloué dynamiquement. Ainsi que le B
. Le a
type de est modifié sans problème.
Deuxième cas:
o.__class__ = B
Le type de o
est le type intégré object
( PyBaseObject_Type
). Ce n'est pas du type tas, donc le TypeError
est levé:
TypeError: __class__ assignment only supported for heap types or ModuleType subclasses.