Что такое атрибут __dict __.__ dict__ класса Python? - PullRequest
75 голосов
/ 02 февраля 2011
>>> 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__, и когда оно содержит что-то?

Ответы [ 4 ]

96 голосов
/ 02 февраля 2011

Прежде всего 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__ является специальным прокси-объектом, который притворяется словарём (но может и не быть внутренним) и не позволяет вам изменять его или заменять другим. , Этот прокси-сервер позволяет вам, помимо всего прочего, получать доступ к атрибутам класса, которые специфичны для него и не определены в одной из его баз.

По умолчанию 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).

8 голосов
/ 02 февраля 2011

Поскольку A.__dict__ - это словарь, в котором хранятся атрибуты A, A.__dict__['__dict__'] - это прямая ссылка на этот же атрибут A.__dict__.

A.__dict__ содержит (своего рода) ссылку на себя. Часть "kind-of" - это то, почему выражение 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'
7 голосов
/ 02 февраля 2011

Позволяет немного изучить!

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

6 голосов
/ 07 июня 2016

Вы можете попробовать следующий простой пример, чтобы понять больше этого:

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