2016-07-29 11 views
4

ich über dieses Verhalten gestolpert für Doppelstrich Namen, die ich nicht verstehe:Wie unterscheidet sich __mro__ von anderen doppelten Unterstrichnamen?

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' 

Ich weiß über den Namen für __name Mangeln, die für __name__ (mehr für Überlastung Operator Methoden) nicht anwendbar ist. __id__ verhält sich genau wie eine reguläre Klassenvariable, auf die über den Klassennamen und die Instanz zugegriffen werden kann.

kann jedoch __mro__ nur über Klassenname zugegriffen werden und in der Tat kann ich auch ausdrücklich __mro__ in C vorstellen:

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

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

Ich möchte verstehen, wenn dieses Verhalten einige Python interne Magie ist oder es kann in regulärem Python-Code erreicht werden.

[Python-Version 3.4.3]

Antwort

4

Das hat mit der Suchreihenfolge zu tun.

Lässt man descriptors beiseite, überprüft Python zuerst die Objekte __dict__, um ein Attribut zu finden. Wenn es nicht gefunden werden kann, sucht es nach der Klasse des Objekts und den Basen der Klasse, um das Attribut zu finden. Wenn es dort auch nicht gefunden werden kann, wird AttributeError ausgelöst.

Dies ist wahrscheinlich nicht verständlich, so lassen Sie uns mit einem kurzen Beispiel zeigen dies:

#!/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")) 

Die Ausgabe lautet:

X on Foo True 
X on Bar True 
X on baz False 
Y on Foo False 
Y on Bar True 
Y on baz True 

Wie Sie sehen können, X auf der erklärt wurde MetaklasseFoo. Es ist durch die Instanz des metaclass, die Klasse Bar, aber nicht auf der Instanz baz von Bar, weil es nur in den __dict__ in Foo, nicht in den __dict__ von Bar oder baz ist. Python überprüft nur einen Schritt in der Hierarchie "Meta".

Weitere Informationen über Metaklassen Magie finden Sie in den ausgezeichneten Antworten auf die Frage What is a metaclass in python?.

Dies ist jedoch nicht ausreichend, um das Verhalten zu beschreiben, weil __mro__ für jede Instanz von Foo verschieden ist (das heißt, für jede Klasse).

Dies kann mit Deskriptoren erreicht werden. Bevor der Attributname an den Objekten __dict__ nachgeschlagen wird, überprüft python die der Klasse und ihrer Basen, um festzustellen, ob dem Namen ein Deskriptorobjekt zugewiesen ist. Ein Deskriptor ist ein beliebiges Objekt, das eine __get__ method hat. Wenn dies der Fall ist, wird die Deskriptorobjekte Methode aufgerufen und das Ergebnis wird von der Attributsuche zurückgegeben.Mit einem Deskriptor, der einem Attribut der Metaklasse zugewiesen ist, kann das beobachtete Verhalten erreicht werden: Der Deskriptor kann einen anderen Wert basierend auf dem -Instanz-Argument zurückgeben, aber das Attribut kann nur über die Klasse und die Metaklasse, nicht Instanzen, erreicht werden der Klasse.

Ein Paradebeispiel für Deskriptoren ist property. Hier ist ein einfaches Beispiel mit einem Beschreiber, die das gleiche Verhalten wie __mro__ hat:

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) 

Die Ausgabe lautet:

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'> 

Wie Sie sehen können, OtherBar und OtherFoo beide haben die Z zugänglich Attribut, aber other_baz nicht. Dennoch kann Z einen anderen Wert für jede OtherFoo Instanz haben, dh jede Klasse, die die Metaklasse OtherFoo verwendet.

Metaklassen sind zunächst verwirrend, und noch mehr, wenn Deskriptoren im Spiel sind. Ich schlage vor, über Metaklassen die linked question, sowie Deskriptoren in Python im Allgemeinen zu lesen.

+2

Die letzte Ausgabe Box sieht aus wie Sie den falschen Block kopieren eingefügt :) – acdr

+0

@acdr Fest, danke! –

+0

@Jonas, wusste nicht über Metaklasse, so dauerte eine Weile, um Ihre Antwort zu verstehen. Zusammenfassend ist "__mro__" ein Attribut, das in einer Metaklasse definiert ist, auf die nur über die Klasse zugegriffen werden kann, die eine Instanz der genannten Metaklasse ist. Darüber hinaus ist es als Deskriptor definiert und hat somit Vorrang vor dem explizit für die Klasse definierten '__mro__'. Richtig? –