Що таке атрибут __dict __.__ dict__ класу Python?


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?

Якщо я це роблю A.something = 10, це йде в A.__dict__. Що це це <attribute '__dict__' of 'A' objects>Знайдений в A.__dict__.__dict__, і коли воно містить що - то?


11
Більш підходящим прикладом змінної був би ive. Принаймні, це зробило б ще більше A.__dict__['ive']запитання;) Я себе
виберу

Відповіді:


110

Перш за все A.__dict__.__dict__відрізняється від A.__dict__['__dict__'], а першого не існує. Останнє є __dict__атрибутом, який мали б екземпляри класу. Це об’єкт дескриптора, який повертає внутрішній словник атрибутів для конкретного екземпляра. Коротше кажучи, __dict__атрибут об'єкта не може зберігатися в об'єкті __dict__, тому до нього можна отримати доступ через дескриптор, визначений у класі.

Щоб це зрозуміти, вам доведеться прочитати документацію протоколу дескрипторів .

Коротка версія:

  1. Для екземпляра класу Aдоступ до instance.__dict__якого надається A.__dict__['__dict__']таким самим, що і vars(A)['__dict__'].
  2. Для класу A доступ до A.__dict__забезпечує type.__dict__['__dict__'](теоретично), що є таким самим, як vars(type)['__dict__'].

Довга версія:

І класи, і об'єкти забезпечують доступ до атрибутів як через оператор атрибутів (реалізований через клас або метаклас __getattribute__), так і через __dict__атрибут / протокол, який використовується vars(ob).

Для звичайних об'єктів __dict__об'єкт створює окремий dictоб'єкт, який зберігає атрибути, і __getattribute__спочатку намагається отримати до нього доступ і отримати атрибути звідти (перед спробою шукати атрибут у класі за допомогою протоколу дескриптора та перед викликом __getattr__). __dict__Дескриптор на клас реалізує доступ до цього словника.

  • x.nameрівносильна спробі тих , в наступному порядку: x.__dict__['name'], type(x).name.__get__(x, type(x)),type(x).name
  • x.__dict__ робить те саме, але пропускає перший із зрозумілих причин

Як це неможливо для __dict__з instanceзберігати в __dict__екземплярі, він доступний через протокол дескриптора безпосередньо замість цього, і зберігається в спеціальному полі в екземплярі.

Подібний сценарій справедливий для класів, хоча вони __dict__є спеціальним проксі-об'єктом, який видає себе за словник (але може бути не внутрішнім) і не дозволяє змінити його або замінити іншим. Цей проксі дозволяє, крім усього іншого, отримувати доступ до атрибутів класу, які є специфічними для нього і не визначені в одній з його баз.

За замовчуванням a vars(cls)порожнього класу містить три дескриптори - __dict__для зберігання атрибутів екземплярів, __weakref__що використовуються всередині weakref, та документації класу. Перші два можуть зникнути, якщо ви визначите __slots__. Тоді у вас не було б атрибутів __dict__і __weakref__атрибутів, але натомість у вас був би один атрибут класу для кожного слота. Тоді атрибути екземпляра не будуть зберігатися у словнику, і доступ до них забезпечуватимуть відповідні дескриптори класу.


І нарешті, невідповідність, яка A.__dict__відрізняється від, A.__dict__['__dict__']полягає в тому, що атрибут __dict__, за винятком, ніколи не шукався vars(A), тому те, що для нього відповідає дійсності, не відповідає практично будь-якому іншому атрибуту, який ви використовуєте. Наприклад, A.__weakref__це те саме, що A.__dict__['__weakref__']. Якби цієї невідповідності не існувало, використання A.__dict__не працювало б, і vars(A)замість цього вам довелося б завжди використовувати .


6
Дякую за детальну відповідь. Незважаючи на те, що мені довелося прочитати його кілька разів, я думаю, що минув деякий час, коли я дізнався так багато нових деталей Python.
porgarmingduod

Чому саме не __dict__можна зберігати атрибут об’єкта в об’єкті __dict__?
zumgruenenbaum

2
@zumgruenenbaum Оскільки __dict__призначений для зберігання всіх атрибутів екземпляра, доступ до атрибута форми obj.xврешті-решт шукається на об'єкті __dict__, а саме obj.__dict__['x']. Тепер, якби __dict__не був реалізований як дескриптор, це призвело б до нескінченної рекурсії, оскільки для доступу obj.__dict__вам потрібно буде шукати його як obj.__dict__['__dict__']. Дескриптор обходить цю проблему.
a_guest

11

Оскільки A.__dict__словник зберігає Aатрибути, A.__dict__['__dict__']це пряме посилання на той самий A.__dict__атрибут.

A.__dict__містить (своєрідне) посилання на себе. Частина "типу" - це те, чому вираз A.__dict__повертає dictproxyзамість нормального значення 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'

9
A.__dict__['__dict__']не є посиланням на A.__dict__. Він реалізує __dict__атрибут екземплярів. Щоб спробувати це самостійно, A.__dict__['__dict__'].__get__(A(), A)повертає атрибути A(), в той час як A.__dict__['__dict__'].__get__(A, type)не вдається.
Рош Оксиморон

10

Давайте трохи дослідимо!

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

Цікаво, що це?

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

Які атрибути має getset_descriptorоб’єкт?

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

Зробивши копію цього, dictproxyми можемо знайти деякі цікаві атрибути, зокрема __objclass__і __name__.

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

Тож __objclass__посилання на Aі __name__це лише рядок '__dict__', назва атрибута, можливо?

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

Ось у нас це! A.__dict__['__dict__']- це об’єкт, на який можна повернутися A.__dict__.


PEP 252 говорить, що __objclass__це клас, який визначив цей атрибут, а не це атрибут цього класу. Це робить ваш getattrприклад неправильним. Більш правильним будеgetattr(A().__dict__['__dict__'].__objclass__, A.__dict__['__dict__'].__name__)
Рош Оксиморон

1
@RoshOxymoron Вираз обличчя піднімається KeyError: '__dict__', всупереч виразів @ AndrewClark.
Maggyero

9

Ви можете спробувати наступний простий приклад, щоб зрозуміти більше цього:

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

З наведеного вище прикладу здається, що атрибути об'єктів класу зберігаються їх класом, атрибути класу зберігаються їх класом, які є метакласами. Це також підтверджується:

>>> a.__dict__ == A.__getattribute__(a, '__dict__')
True
>>> A.__dict__ == type.__getattribute__(A, '__dict__')
True

1
Як не дивно, якщо isзамінено на ==у другому порівнянні, тобто A.__dict__ is type.__dict__['__dict__'].__get__(A)результат є Falseяк у python 2.7.15+, так і в 3.6.8.
Arne Vogel
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.