O que é o atributo __dict __.__ dict__ de uma classe Python?

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

Se eu fizer A.something = 10isso, isso vai para A.__dict__. O que é esta <attribute '__dict__' of 'A' objects>encontrada em A.__dict__.__dict__, e quando ele contém alguma coisa?

Porgarmingduod
fonte
11
Um exemplo de variável mais adequado teria sido ive. Pelo menos isso teria feito mais uma A.__dict__['ive']pergunta;) Vou me ver sair
Joakim

Respostas:

109

Em primeiro lugar, A.__dict__.__dict__é diferente de A.__dict__['__dict__'], e o primeiro não existe. O último é o __dict__atributo que as instâncias da classe teriam. É um objeto descritor que retorna o dicionário interno de atributos para a instância específica. Resumindo, o __dict__atributo de um objeto não pode ser armazenado nos objetos __dict__, portanto é acessado por meio de um descritor definido na classe.

Para entender isso, você teria que ler a documentação do protocolo do descritor .

A versão curta:

  1. Para uma instância de classe A, o acesso a instance.__dict__é fornecido por A.__dict__['__dict__']qual é o mesmo que vars(A)['__dict__'].
  2. Para a classe A, o acesso a A.__dict__é fornecido por type.__dict__['__dict__'](em teoria) que é o mesmo que vars(type)['__dict__'].

A versão longa:

Ambas as classes e objetos fornecem acesso a atributos por meio do operador de atributo (implementado por meio da classe ou metaclasse __getattribute__) e do __dict__atributo / protocolo que é usado por vars(ob).

Para objetos normais, o __dict__objeto cria um dictobjeto separado , que armazena os atributos e __getattribute__primeiro tenta acessá-lo e obter os atributos de lá (antes de tentar procurar o atributo na classe utilizando o protocolo descritor e antes de chamar __getattr__). O __dict__descritor da classe implementa o acesso a este dicionário.

  • x.nameé equivalente a tentar aqueles em ordem: x.__dict__['name'], type(x).name.__get__(x, type(x)),type(x).name
  • x.__dict__ faz o mesmo, mas pula o primeiro por razões óbvias

Como é impossível para o __dict__de instanceser armazenado em __dict__da instância, que é acessado através do protocolo descritor diretamente em vez, e é armazenado em um campo especial na instância.

Um cenário semelhante é verdadeiro para classes, embora __dict__sejam um objeto proxy especial que finge ser um dicionário (mas pode não ser internamente) e não permite que você o altere ou substitua por outro. Este proxy permite, entre tudo mais, acessar os atributos de uma classe que lhe são específicos, e não definidos em uma de suas bases.

Por padrão, um vars(cls)de uma classe vazia carrega três descritores - __dict__para armazenar os atributos das instâncias, __weakref__que é usado internamente por weakrefe a docstring da classe. Os dois primeiros podem ter desaparecido se você definir __slots__. Então você não teria os atributos __dict__e __weakref__, mas em vez disso, você teria um único atributo de classe para cada slot. Os atributos da instância não seriam armazenados em um dicionário, e o acesso a eles seria fornecido pelos respectivos descritores da classe.


E, por último, a inconsistência que A.__dict__é diferente de A.__dict__['__dict__']é porque o atributo __dict__, por exceção, nunca é pesquisado vars(A), então o que é verdade para ele não é verdade para praticamente qualquer outro atributo que você usaria. Por exemplo, A.__weakref__é a mesma coisa que A.__dict__['__weakref__']. Se essa inconsistência não existisse, usar A.__dict__não funcionaria e você teria que usar sempre vars(A).

Rosh Oxymoron
fonte
6
Obrigado pela resposta detalhada. Embora eu tenha que ler algumas vezes, acho que já faz um tempo que não aprendi tantos detalhes novos sobre Python.
porgarmingduod
Por que exatamente o __dict__atributo de um objeto não pode ser armazenado no do objeto __dict__?
zumgruenenbaum
2
@zumgruenenbaum Como __dict__se destina a armazenar todos os atributos de instância, um acesso de atributo do formulário obj.xé eventualmente procurado no objeto __dict__, a saber obj.__dict__['x']. Agora, se __dict__não foi implementado como um descritor, isso levaria a uma recursão infinita, pois para acessar obj.__dict__você precisaria procurá-lo como obj.__dict__['__dict__']. O descritor contorna esse problema.
a_guest
10

Visto que A.__dict__é um dicionário que armazena Aatributos, A.__dict__['__dict__']é a referência direta a esse mesmo A.__dict__atributo.

A.__dict__contém uma (espécie de) referência a si mesmo. A parte "tipo-de" é porque a expressão A.__dict__retorna um em dictproxyvez de um normal 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
fonte
9
A.__dict__['__dict__']não é uma referência a A.__dict__. Ele implementa o __dict__atributo das instâncias. Para tentar você mesmo, A.__dict__['__dict__'].__get__(A(), A)retorna os atributos de A(), enquanto A.__dict__['__dict__'].__get__(A, type)falha.
Rosh Oxymoron
10

Vamos explorar!

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

Eu imagino o que isso seja?

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

Quais atributos um getset_descriptorobjeto possui?

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

Fazendo uma cópia disso dictproxy, podemos encontrar alguns atributos interessantes, especificamente __objclass__e __name__.

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

Portanto, __objclass__é uma referência Ae __name__é apenas a string '__dict__', o nome de um atributo, talvez?

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

Aí está! A.__dict__['__dict__']é um objeto ao qual pode se referir A.__dict__.

Andrew Clark
fonte
O PEP 252 diz que __objclass__é a classe que definiu este atributo, não que é um atributo dessa classe. Isso torna seu getattrexemplo incorreto. Um mais correto seriagetattr(A().__dict__['__dict__'].__objclass__, A.__dict__['__dict__'].__name__)
Rosh Oxymoron
1
@RoshOxymoron Sua expressão aumenta KeyError: '__dict__', ao contrário de @AndrewClark.
Maggyero
9

Você pode tentar o seguinte exemplo simples para entender mais sobre isso:

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

Pelo exemplo acima, parece que os atributos dos objetos de classe são armazenados por sua classe, os atributos de classe são armazenados por sua classe, que são metaclasses. Isso também é validado por:

>>> a.__dict__ == A.__getattribute__(a, '__dict__')
True
>>> A.__dict__ == type.__getattribute__(A, '__dict__')
True
damaZhang
fonte
1
Estranhamente, if isé substituído ==na segunda comparação, ou seja A.__dict__ is type.__dict__['__dict__'].__get__(A), o resultado está Falseem python 2.7.15+ e 3.6.8.
Arne Vogel