Einfache Reproduktion:
class VocalDescriptor(object):
def __get__(self, obj, objtype):
print('__get__, obj={}, objtype={}'.format(obj, objtype))
def __set__(self, obj, val):
print('__set__')
class B(object):
v = VocalDescriptor()
B.v # prints "__get__, obj=None, objtype=<class '__main__.B'>"
B.v = 3 # does not print "__set__", evidently does not trigger descriptor
B.v # does not print anything, we overwrote the descriptor
Diese Frage hat ein effektives Duplikat , aber das Duplikat wurde nicht beantwortet, und ich habe mich als Lernübung etwas mehr mit der CPython-Quelle befasst. Achtung: Ich bin ins Unkraut gegangen. Ich hoffe wirklich, dass ich Hilfe von einem Kapitän bekommen kann, der diese Gewässer kennt . Ich habe versucht, die Anrufe, die ich mir ansah, so explizit wie möglich zu verfolgen, zu meinem eigenen zukünftigen Nutzen und zum Nutzen zukünftiger Leser.
Ich habe viel Tinte über das Verhalten von __getattribute__
Deskriptoren verschüttet gesehen , z. B. Vorrang bei der Suche. Die Python - Code - Schnipsel in „Hervorrufen Descriptors“ knapp unter For classes, the machinery is in type.__getattribute__()...
etwa in meinem Kopf stimmt , was ich glaube , dass die entsprechende CPython Quelle in type_getattro
, die ich durch einen Blick auf aufgespürt „tp_slots“ dann wo tp_getattro bevölkert ist . Und die Tatsache, dass B.v
zunächst gedruckt wird, __get__, obj=None, objtype=<class '__main__.B'>
macht für mich Sinn.
Was ich nicht verstehe ist, warum B.v = 3
überschreibt die Zuweisung den Deskriptor blind, anstatt ihn auszulösen v.__set__
? Ich habe versucht, den CPython-Aufruf zu verfolgen, indem ich noch einmal von "tp_slots" aus angefangen habe , dann nachgesehen habe , wo tp_setattro gefüllt ist , und dann nach type_setattro . type_setattro
scheint ein dünner Wrapper um _PyObject_GenericSetAttrWithDict zu sein . Und da ist der Kern meiner Verwirrung: Es _PyObject_GenericSetAttrWithDict
scheint eine Logik__set__
zu haben , die der Methode eines Deskriptors Vorrang einräumt !! Vor diesem Hintergrund kann ich nicht herausfinden, warum B.v = 3
blind überschrieben v
und nicht ausgelöst wird v.__set__
.
Haftungsausschluss 1: Ich habe Python nicht mit printfs aus dem Quellcode neu erstellt, daher bin ich mir nicht ganz sicher, type_setattro
wie es während des Aufrufs aufgerufen wird B.v = 3
.
Haftungsausschluss 2: VocalDescriptor
soll nicht als Beispiel für eine "typische" oder "empfohlene" Deskriptordefinition dienen. Es ist ein ausführliches No-Op, mir zu sagen, wann die Methoden aufgerufen werden.
quelle
__get__
überhaupt gearbeitet hat und nicht warum__set__
.__get__
Methode weiterhin aufgerufen wird .B.v = 3
hat das Attribut effektiv mit einem überschriebenint
.__get__
genannt wird , und die Standard - Implementierungenobject.__getattribute__
undtype.__getattribute__
invoke ,__get__
wenn eine Instanz oder die Klasse. Das Zuweisen über__set__
ist nur eine Instanz.__get__
Methoden der Deskriptoren sollen ausgelöst werden, wenn sie von der Klasse selbst aufgerufen werden. Auf diese Weise werden @classmethods und @staticmethods gemäß der Anleitung implementiert . @Jab Ich frage mich, warumB.v = 3
der Klassendeskriptor überschrieben werden kann. Basierend auf der CPython-Implementierung hatte ich erwartetB.v = 3
, auch auszulösen__set__
.Antworten:
Sie haben Recht, dass
B.v = 3
der Deskriptor einfach mit einer Ganzzahl überschrieben wird (wie es sollte).Um
B.v = 3
einen Deskriptor aufzurufen, sollte der Deskriptor in der Metaklasse definiert worden sein, dh intype(B)
.Um den Deskriptor aufzurufen
B
, würden Sie eine Instanz verwenden:B().v = 3
wird es tun.Der Grund für das
B.v
Aufrufen des Getters besteht darin, die Rückgabe der Deskriptorinstanz selbst zuzulassen. Normalerweise würden Sie dies tun, um den Zugriff auf den Deskriptor über das Klassenobjekt zu ermöglichen:Jetzt
B.v
würde eine Instanz zurückgegeben, mit<mymodule.VocalDescriptor object at 0xdeadbeef>
der Sie interagieren können. Es ist buchstäblich das Deskriptorobjekt, das als Klassenattribut definiert ist, und sein StatusB.v.__dict__
wird von allen Instanzen von gemeinsam genutztB
.Natürlich liegt es am Code des Benutzers, genau zu definieren, was
B.v
er tun möchte. Die Rückgabeself
ist nur das übliche Muster.quelle
__get__
sie als Instanzattribut oder Klassenattribut aufgerufen werden soll, aber__set__
nur als Instanzattribut. Und relevante Dokumente: docs.python.org/3/reference/datamodel.html#object.__get___PyObject_GenericSetAttrWithDict
es die Py_TYPE von B zieht , wietp
, die Metaklasse B ist (type
in meinem Fall), dann ist es die Metaklassetp
, die durch die behandelt wird Deskriptors kurzzuschließen Logik . Der direkt definierte DeskriptorB
wird also von dieser Kurzschlusslogik nicht gesehen (daher wird in meinem ursprünglichen Code__set__
nicht aufgerufen), aber ein in der Metaklasse definierter Deskriptor wird von der Kurzschlusslogik gesehen.__set__
Verfahren dieses Descriptor wird genannt.Das Ausschließen von Überschreibungen
B.v
ist äquivalent zutype.__getattribute__(B, "v")
, währendb = B(); b.v
äquivalent zu istobject.__getattribute__(b, "v")
. Beide Definitionen rufen die__get__
Methode des Ergebnisses auf, falls definiert.Beachten Sie, dass der Aufruf von
__get__
in jedem Fall unterschiedlich ist.B.v
wirdNone
als erstes Argument übergeben, währendB().v
die Instanz selbst übergeben wird. In beiden FällenB
wird als zweites Argument übergeben.B.v = 3
ist andererseits äquivalent zutype.__setattr__(B, "v", 3)
, was nicht aufruft__set__
.quelle