>>> 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 = 10
isso, 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?
python
class
metaprogramming
magic-methods
Porgarmingduod
fonte
fonte
ive
. Pelo menos isso teria feito mais umaA.__dict__['ive']
pergunta;) Vou me ver sairRespostas:
Em primeiro lugar,
A.__dict__.__dict__
é diferente deA.__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:
A
, o acesso ainstance.__dict__
é fornecido porA.__dict__['__dict__']
qual é o mesmo quevars(A)['__dict__']
.A.__dict__
é fornecido portype.__dict__['__dict__']
(em teoria) que é o mesmo quevars(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 porvars(ob)
.Para objetos normais, o
__dict__
objeto cria umdict
objeto 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 óbviasComo é impossível para o
__dict__
deinstance
ser 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 porweakref
e 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 deA.__dict__['__dict__']
é porque o atributo__dict__
, por exceção, nunca é pesquisadovars(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 queA.__dict__['__weakref__']
. Se essa inconsistência não existisse, usarA.__dict__
não funcionaria e você teria que usar semprevars(A)
.fonte
__dict__
atributo de um objeto não pode ser armazenado no do objeto__dict__
?__dict__
se destina a armazenar todos os atributos de instância, um acesso de atributo do formulárioobj.x
é eventualmente procurado no objeto__dict__
, a saberobj.__dict__['x']
. Agora, se__dict__
não foi implementado como um descritor, isso levaria a uma recursão infinita, pois para acessarobj.__dict__
você precisaria procurá-lo comoobj.__dict__['__dict__']
. O descritor contorna esse problema.Visto que
A.__dict__
é um dicionário que armazenaA
atributos,A.__dict__['__dict__']
é a referência direta a esse mesmoA.__dict__
atributo.A.__dict__
contém uma (espécie de) referência a si mesmo. A parte "tipo-de" é porque a expressãoA.__dict__
retorna um emdictproxy
vez de um normaldict
.fonte
A.__dict__['__dict__']
não é uma referência aA.__dict__
. Ele implementa o__dict__
atributo das instâncias. Para tentar você mesmo,A.__dict__['__dict__'].__get__(A(), A)
retorna os atributos deA()
, enquantoA.__dict__['__dict__'].__get__(A, type)
falha.Vamos explorar!
Eu imagino o que isso seja?
Quais atributos um
getset_descriptor
objeto possui?Fazendo uma cópia disso
dictproxy
, podemos encontrar alguns atributos interessantes, especificamente__objclass__
e__name__
.Portanto,
__objclass__
é uma referênciaA
e__name__
é apenas a string'__dict__'
, o nome de um atributo, talvez?Aí está!
A.__dict__['__dict__']
é um objeto ao qual pode se referirA.__dict__
.fonte
__objclass__
é a classe que definiu este atributo, não que é um atributo dessa classe. Isso torna seugetattr
exemplo incorreto. Um mais correto seriagetattr(A().__dict__['__dict__'].__objclass__, A.__dict__['__dict__'].__name__)
KeyError: '__dict__'
, ao contrário de @AndrewClark.Você pode tentar o seguinte exemplo simples para entender mais sobre isso:
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:
fonte
is
é substituído==
na segunda comparação, ou sejaA.__dict__ is type.__dict__['__dict__'].__get__(A)
, o resultado estáFalse
em python 2.7.15+ e 3.6.8.