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.
Die letzte Ausgabe Box sieht aus wie Sie den falschen Block kopieren eingefügt :) – acdr
@acdr Fest, danke! –
@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? –