Co to jest atrybut __dict __.__ dict__ klasy Pythona?

92
>>> class A(object): pass
... 
>>> A.__dict__
<dictproxy object at 0x173ef30>
>>> A.__dict__.__dict__
Traceback (most recent call last):
  File "<string>", line 1, in <fragment>
AttributeError: 'dictproxy' object has no attribute '__dict__'
>>> A.__dict__.copy()
{'__dict__': <attribute '__dict__' of 'A' objects> ... }
>>> A.__dict__['__dict__']
<attribute '__dict__' of 'A' objects> # What is this object?

Jeśli tak A.something = 10, to przechodzi do A.__dict__. Co jest tego <attribute '__dict__' of 'A' objects>znaleźć w A.__dict__.__dict__, i kiedy to ma zawierać coś?

porgarmingduod
źródło
11
Bardziej odpowiednia byłaby przykładowa zmienna ive. Przynajmniej sprawiłoby to więcej A.__dict__['ive']pytań;) zobaczę się poza
domem

Odpowiedzi:

110

Przede wszystkim A.__dict__.__dict__jest inny A.__dict__['__dict__'], a tego pierwszego nie ma. Ten ostatni jest __dict__atrybutem, który miałyby wystąpienia klasy. Jest to obiekt deskryptora, który zwraca wewnętrzny słownik atrybutów dla określonej instancji. Krótko mówiąc, __dict__atrybut obiektu nie może być przechowywany w obiekcie __dict__, więc jest dostępny za pośrednictwem deskryptora zdefiniowanego w klasie.

Aby to zrozumieć, musiałbyś przeczytać dokumentację protokołu deskryptora .

Krótka wersja:

  1. W przypadku klasy A, dostęp instance.__dict__jest zapewniany przez A.__dict__['__dict__']to samo, co vars(A)['__dict__'].
  2. Dla klasy A dostęp A.__dict__zapewnia type.__dict__['__dict__'](w teorii) to samo, co vars(type)['__dict__'].

Wersja długa:

Zarówno klasy, jak i obiekty zapewniają dostęp do atrybutów zarówno za pośrednictwem operatora atrybutu (zaimplementowanego za pośrednictwem klasy lub metaklasy __getattribute__), jak i __dict__atrybutu / protokołu, który jest używany przez vars(ob).

W przypadku normalnych obiektów __dict__obiekt tworzy oddzielny dictobiekt, który przechowuje atrybuty i __getattribute__najpierw próbuje uzyskać do niego dostęp i stamtąd pobrać atrybuty (przed próbą wyszukania atrybutu w klasie przy użyciu protokołu deskryptora i przed wywołaniem __getattr__). __dict__Deskryptor od klasy implementuje dostęp do tego słownika.

  • x.namejest równoważne próbuje te w kolejności: x.__dict__['name'], type(x).name.__get__(x, type(x)),type(x).name
  • x.__dict__ robi to samo, ale pomija pierwszy z oczywistych powodów

Jak to jest możliwe, aby __dict__z instancebyć przechowywane w __dict__instancji, jest on dostępny za pośrednictwem protokołu deskryptora zamiast bezpośrednio, i jest przechowywany w specjalnym polu w instancji.

Podobny scenariusz jest prawdziwy dla klas, chociaż ich __dict__jest specjalnym obiektem proxy, który udaje słownik (ale może nie być wewnętrznie) i nie pozwala na jego zmianę lub zastąpienie innym. To proxy umożliwia między innymi dostęp do atrybutów klasy, które są dla niej specyficzne i nie są zdefiniowane w żadnej z jej baz.

Domyślnie vars(cls)klasa pustej zawiera trzy deskryptory - __dict__do przechowywania atrybutów instancji, __weakref__które są używane wewnętrznie przez weakrefklasę, oraz do jej opisu. Pierwsze dwa mogą zniknąć, jeśli zdefiniujesz __slots__. Wtedy nie miałbyś atrybutów __dict__i __weakref__, ale zamiast tego miałbyś jeden atrybut klasy dla każdego gniazda. Atrybuty instancji nie byłyby wtedy przechowywane w słowniku, a dostęp do nich zapewnią odpowiednie deskryptory w klasie.


I wreszcie, niespójność, która A.__dict__różni się od A.__dict__['__dict__']tego, że atrybut __dict__jest wyjątkowy, nigdy nie jest sprawdzany vars(A), więc to, co jest prawdą, nie jest prawdą dla praktycznie każdego innego atrybutu, którego użyjesz. Na przykład A.__weakref__to to samo, co A.__dict__['__weakref__']. Gdyby ta niespójność nie istniała, używanie A.__dict__nie działałoby i musiałbyś zawsze używać vars(A)zamiast tego.

Rosz Oksymoron
źródło
6
Dzięki za szczegółową odpowiedź. Chociaż musiałem to przeczytać kilka razy, myślę, że minęło trochę czasu, odkąd nauczyłem się tak wielu nowych szczegółów Pythona.
porgarmingduod
Dlaczego dokładnie nie __dict__można przechowywać atrybutu obiektu w atrybucie obiektu __dict__?
zumgruenenbaum
2
@zumgruenenbaum Ponieważ __dict__ma na celu przechowywanie wszystkich atrybutów instancji, dostęp do atrybutu formularza obj.xjest ostatecznie sprawdzany w obiekcie __dict__, a mianowicie obj.__dict__['x']. Teraz, gdyby __dict__nie został zaimplementowany jako deskryptor, doprowadziłoby to do nieskończonej rekurencji, ponieważ aby uzyskać dostęp obj.__dict__, musiałbyś sprawdzić to jako obj.__dict__['__dict__']. Deskryptor omija ten problem.
a_guest
11

Ponieważ A.__dict__jest słownikiem przechowującym Aatrybuty, A.__dict__['__dict__']jest bezpośrednim odniesieniem do tego samego A.__dict__atrybutu.

A.__dict__zawiera (rodzaj) odniesienie do siebie. Część typu „rodzaj” jest powodem, dla którego wyrażenie A.__dict__zwraca a dictproxyzamiast normalnej dict.

>>> class B(object):
...     "Documentation of B class"
...     pass
...
>>> B.__doc__
'Documentation of B class'
>>> B.__dict__
<dictproxy object at 0x00B83590>
>>> B.__dict__['__doc__']
'Documentation of B class'
vz0
źródło
9
A.__dict__['__dict__']nie jest odniesieniem do A.__dict__. Implementuje __dict__atrybut instancji. Aby spróbować tego samodzielnie, A.__dict__['__dict__'].__get__(A(), A)zwraca atrybuty A(), while A.__dict__['__dict__'].__get__(A, type)kończy się niepowodzeniem.
Rosh Oxymoron
10

Zróbmy trochę odkrywania!

>>> A.__dict__['__dict__']
<attribute '__dict__' of 'A' objects>

Zastanawiam się, co to jest?

>>> type(A.__dict__['__dict__'])
<type 'getset_descriptor'>

Jakie atrybuty ma getset_descriptorobiekt?

>>> type(A.__dict__["__dict__"]).__dict__
<dictproxy object at 0xb7efc4ac>

Tworząc kopię tego dictproxymożemy znaleźć kilka interesujących atrybutów, __objclass__a konkretnie i __name__.

>>> A.__dict__['__dict__'].__objclass__, A.__dict__['__dict__'].__name__
(<class '__main__.A'>, '__dict__')

Czy __objclass__jest więc odniesieniem do łańcucha , Aa __name__może tylko '__dict__'nazwą atrybutu?

>>> getattr(A.__dict__['__dict__'].__objclass__, A.__dict__['__dict__'].__name__) == A.__dict__
True

Mamy to! A.__dict__['__dict__']to obiekt, do którego można się odwołać A.__dict__.

Andrew Clark
źródło
PEP 252 mówi, że __objclass__to klasa definiuje ten atrybut, a nie jest to atrybut tej klasy. To sprawia, że ​​twój getattrprzykład jest nieprawidłowy. Bardziej poprawne byłobygetattr(A().__dict__['__dict__'].__objclass__, A.__dict__['__dict__'].__name__)
Rosz Oksymoron
1
@RoshOxymoron Twoja ekspresja podnosi się KeyError: '__dict__', w przeciwieństwie do @ AndrewClark.
Maggyero
9

Możesz spróbować następującego prostego przykładu, aby lepiej to zrozumieć:

>>> class A(object): pass
... 
>>> a = A()
>>> type(A)
<type 'type'>
>>> type(a)
<class '__main__.A'>
>>> type(a.__dict__)
<type 'dict'>
>>> type(A.__dict__)
<type 'dictproxy'>
>>> type(type.__dict__)
<type 'dictproxy'>
>>> type(A.__dict__['__dict__'])
<type 'getset_descriptor'>
>>> type(type.__dict__['__dict__'])
<type 'getset_descriptor'>
>>> a.__dict__ == A.__dict__['__dict__'].__get__(a)
True
>>> A.__dict__ == type.__dict__['__dict__'].__get__(A)
True
>>> a.__dict__ == type.__dict__['__dict__'].__get__(A)['__dict__'].__get__(a)
True

Z powyższego przykładu wynika, że ​​atrybuty obiektów klas są przechowywane przez ich klasę, atrybuty klas są przechowywane przez ich klasę, którą są metaklasy. Jest to również potwierdzone przez:

>>> a.__dict__ == A.__getattribute__(a, '__dict__')
True
>>> A.__dict__ == type.__getattribute__(A, '__dict__')
True
damaZhang
źródło
1
O dziwo, jeśli w drugim porównaniu iszostanie zastąpione ==, tj A.__dict__ is type.__dict__['__dict__'].__get__(A). Wynik jest Falsezarówno w Pythonie 2.7.15+, jak i 3.6.8.
Arne Vogel