Что такое атрибут __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'
vz0
источник
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
DamaZhang
источник
1
Как ни странно, if isзаменяется на ==во втором сравнении, т.е. A.__dict__ is type.__dict__['__dict__'].__get__(A)результат есть Falseкак в python 2.7.15+, так и в 3.6.8.
Arne Vogel