Cela a à voir avec l'ordre de recherche.
En laissant descriptors de côté, python vérifie d'abord les objets __dict__
pour trouver un attribut. S'il ne peut pas le trouver, il regardera la classe de l'objet et les bases de la classe pour trouver l'attribut. Si elle ne peut pas être trouvée là non plus, AttributeError est déclenché.
Ceci est probablement pas compréhensible, laissez-nous montrer cela avec un court exemple:
#!/usr/bin/python3
class Foo(type):
X = 10
class Bar(metaclass=Foo):
Y = 20
baz = Bar()
print("X on Foo", hasattr(Foo, "X"))
print("X on Bar", hasattr(Bar, "X"))
print("X on baz", hasattr(baz, "X"))
print("Y on Foo", hasattr(Foo, "Y"))
print("Y on Bar", hasattr(Bar, "Y"))
print("Y on baz", hasattr(baz, "Y"))
La sortie est:
X on Foo True
X on Bar True
X on baz False
Y on Foo False
Y on Bar True
Y on baz True
Comme vous pouvez le voir, X
a été déclarée sur le métaclasseFoo
. Il est accessible par l'instance de la métaclasse, la classe Bar
, mais pas sur l'instance baz
de Bar
, car il est seulement dans le __dict__
en Foo
, pas dans le __dict__
de Bar
ou baz
. Python vérifie seulement un accélérer dans la hiérarchie "méta".
Pour en savoir plus sur la magie de la métaclasse, consultez les excellentes réponses à la question What is a metaclass in python?.
Ceci, cependant, ne suffit pas pour décrire le comportement, parce que __mro__
est différent pour chaque instance de Foo
(qui est, pour chaque classe).
Ceci peut être réalisé en utilisant des descripteurs. Avant le nom de l'attribut est recherché sur les objets __dict__
, python vérifie le __dict__
de la classe et ses bases pour voir si un objet descripteur est affecté au nom. Un descripteur est un objet qui a un __get__
method. Si tel est le cas, la méthode des objets descripteur __get__
est appelée et le résultat est renvoyé à partir de la recherche d'attribut.Avec un descripteur assigné à un attribut de la métaclasse, le comportement observé peut être atteint: Le descripteur peut retourner une valeur différente basée sur l'argument instance, mais néanmoins l'attribut ne peut être accédé qu'à travers la classe et la métaclasse, pas les instances de la classe. Un exemple de descripteurs est property
. Voici un exemple simple avec un descripteur qui a le même comportement que __mro__
:
class Descriptor:
def __get__(self, instance, owner):
return "some value based on {}".format(instance)
class OtherFoo(type):
Z = Descriptor()
class OtherBar(metaclass=OtherFoo):
pass
other_baz = OtherBar()
print("Z on OtherFoo", hasattr(OtherFoo, "Z"))
print("Z on OtherBar", hasattr(OtherBar, "Z"))
print("Z on other_baz", hasattr(other_baz, "Z"))
print("value of Z on OtherFoo", OtherFoo.Z)
print("value of Z on OtherBar", OtherBar.Z)
La sortie est:
Z on OtherFoo True
Z on OtherBar True
Z on other_baz False
value of Z on OtherFoo some value based on None
value of Z on OtherBar some value based on <class '__main__.OtherBar'>
Comme vous pouvez le voir, OtherBar
et OtherFoo
ont tous deux l'Z
attribut accessible, mais other_baz
ne fait pas. Néanmoins, Z
peut avoir une valeur différente pour chaque instance OtherFoo
, c'est-à-dire, chaque classe utilisant la métaclasse OtherFoo
.
Les métaclasses sont déroutantes au début, et encore plus lorsque des descripteurs sont en jeu. Je suggère de lire sur les métaclasses linked question, ainsi que des descripteurs en python en général.
La dernière boîte de sortie ressemble à copier-coller le mauvais bloc. :) – acdr
@acdr Correction, merci! –
@Jonas, ne savait pas à propos de la métaclasse, donc pris un moment pour comprendre votre réponse. Pour résumer, '__mro__' est un attribut défini sur une métaclasse qui ne peut être accessible que via la classe qui est une instance de la métaclasse. En plus, il est défini comme un descripteur qui a donc la priorité sur le '__mro__' explicitement défini sur la classe elle-même. Correct? –