Proste repro:
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
To pytanie ma skuteczny duplikat , ale na duplikat nie udzielono odpowiedzi, a jako ćwiczenie edukacyjne wkopałem trochę więcej w źródło CPython. Ostrzeżenie: poszedłem do chwastów. Naprawdę mam nadzieję, że uda mi się uzyskać pomoc od kapitana, który zna te wody . Starałem się jak najdokładniej śledzić rozmowy, na które patrzyłem, dla własnej korzyści i korzyści dla przyszłych czytelników.
Widziałem dużo atramentu rozlanego na zachowanie __getattribute__
stosowane do deskryptorów, np. Pierwszeństwo wyszukiwania. Python snippet w „Wywoływanie Descriptors” tuż poniżej For classes, the machinery is in type.__getattribute__()...
grubsza zgadza się w moim umyśle, co moim zdaniem jest odpowiednie źródło CPython w type_getattro
, które śledzone przez patrząc na „tp_slots” wtedy gdzie tp_getattro jest zaludnionych . A fakt, że B.v
początkowo drukuje, __get__, obj=None, objtype=<class '__main__.B'>
ma dla mnie sens.
Nie rozumiem tylko, dlaczego przypisanie B.v = 3
ślepo zastępuje deskryptor, a nie wyzwala v.__set__
? Próbowałem prześledzić wywołanie CPython, zaczynając od „tp_slots” , następnie sprawdzając, gdzie jest wypełniony tp_setattro , a potem patrząc na type_setattro . type_setattro
wygląda na cienkie opakowanie wokół _PyObject_GenericSetAttrWithDict . I jest sedno mojego zamieszania: _PyObject_GenericSetAttrWithDict
wydaje się, że ma logikę, która daje pierwszeństwo __set__
metodzie deskryptora !! Mając to na uwadze, nie mogę zrozumieć, dlaczego B.v = 3
ślepo zastępuje, v
a nie wyzwala v.__set__
.
Zastrzeżenie 1: Nie przebudowałem Pythona ze źródła za pomocą printfs, więc nie jestem całkowicie pewien, type_setattro
jak to się nazywa B.v = 3
.
Zastrzeżenie 2: VocalDescriptor
nie ma na celu zilustrowania „typowej” lub „zalecanej” definicji deskryptora. Mówienie mi, kiedy metody są wywoływane, jest pełne.
źródło
__get__
w ogóle działało, a nie dlaczego__set__
.__get__
metodę.B.v = 3
skutecznie zastąpił atrybut wartościąint
.__get__
jest wywoływany, a także domyślne implementacjeobject.__getattribute__
itype.__getattribute__
wywołanie__get__
przy użyciu instancji lub klasy. Przypisywanie za pośrednictwem__set__
dotyczy tylko instancji.__get__
metody deskryptorów powinny się uruchamiać po wywołaniu z samej klasy. W ten sposób zaimplementowano metody @classmethods i @staticmethods, zgodnie z instrukcją . @Jab Zastanawiam się, dlaczegoB.v = 3
można zastąpić deskryptor klasy. W oparciu o implementację CPython spodziewałemB.v = 3
się również, że zadziała__set__
.Odpowiedzi:
Masz rację, że
B.v = 3
po prostu nadpisuje deskryptor liczbą całkowitą (tak jak powinno).Aby
B.v = 3
wywołać deskryptor, deskryptor powinien być zdefiniowany na metaklasie, tjtype(B)
. Na .Aby wywołać deskryptor
B
, należy użyć instancji:B().v = 3
zrobi to.Powodem
B.v
wywołania gettera jest umożliwienie zwrócenia samej instancji deskryptora. Zwykle robiłbyś to, aby umożliwić dostęp do deskryptora za pośrednictwem obiektu klasy:Teraz
B.v
zwróci jakieś wystąpienie, z<mymodule.VocalDescriptor object at 0xdeadbeef>
którym możesz wchodzić w interakcje. Jest to dosłownie obiekt deskryptora, zdefiniowany jako atrybut klasy, a jego stanB.v.__dict__
jest wspólny dla wszystkich instancji klasyB
.Oczywiście to od użytkownika zależy,
B.v
czy dokładnie zdefiniuje, co chce zrobić, zwracanieself
to tylko wspólny wzorzec.źródło
__get__
ma on być wywoływany jako atrybut instancji lub atrybut klasy, ale__set__
został zaprojektowany tak, aby wywoływać tylko jako atrybut instancji. I odpowiednie dokumenty: docs.python.org/3/reference/datamodel.html#object.__get___PyObject_GenericSetAttrWithDict
, to ciągnie Py_TYPE z B jakotp
, która jest metaklasa B jest (type
w moim przypadku), to jest to metaklasatp
który jest traktowany przez logikę zwarciowego deskryptora . Tak więc deskryptor zdefiniowany bezpośrednioB
nie jest widziany przez tę logikę zwarcia (dlatego w moim oryginalnym kodzie__set__
nie jest wywoływany), ale deskryptor zdefiniowany na metaklasie jest postrzegany przez logikę zwarcia.__set__
metoda tego deskryptora .Z wyłączeniem wszelkich przesłonięć,
B.v
jest równoważnetype.__getattribute__(B, "v")
, podczas gdyb = B(); b.v
jest równoważne zobject.__getattribute__(b, "v")
. Obie definicje wywołują__get__
metodę wyniku, jeśli została zdefiniowana.Należy pamiętać, że wezwanie do
__get__
różni się w każdym przypadku.B.v
przechodziNone
jako pierwszy argument, aB().v
przekazuje samą instancję. W obu przypadkachB
jest przekazywany jako drugi argument.B.v = 3
z drugiej strony jest równoważne ztype.__setattr__(B, "v", 3)
, który nie wywołuje__set__
.źródło