2016-07-29 2 views
4

je suis tombé sur ce comportement pour le nom double underscore que je ne comprends pas:En quoi __mro__ est-il différent des autres noms de double underscore?

class A: 
    pass 

class B: 
    pass 

class C(A,B): 
    __id__ = 'c' 

c = C() 
print(C.__mro__) # print the method resolution order of class C 
#print(c.__mro__) # AttributeError: 'C' object has no attribute '__mro__' 
print(C.__id__) # print 'c' 
print(c.__id__) # print 'c' 

Je sais sur le nom mutiler pour __name, qui ne demande pas __name__ (plus pour la surcharge des méthodes de l'opérateur). __id__ se comporte exactement comme une variable de classe régulière accessible via le nom de classe et l'instance.

Cependant, __mro__ ne sont accessibles via le nom de classe et en fait je peux même introduire explicitement __mro__ dans C:

class C(A,B): 
    __mro__ = 'bla' 

print(C.__mro__) # print the method resolution order of class C 
print(c.__mro__) # print 'bla' 

Je voudrais comprendre si ce comportement est un peu de magie interne python ou peut-il être réalisé en code python régulier.

[version python 3.4.3]

Répondre

4

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.

+2

La dernière boîte de sortie ressemble à copier-coller le mauvais bloc. :) – acdr

+0

@acdr Correction, merci! –

+0

@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? –