getset_descriptor

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

If I do A.something = 10, this goes into A.__dict__. What is this <attribute '__dict__' of 'A' objects> found in A.__dict__.__dict__, and when does it contain something?

share improve this question
 
3 
A more suitable example variable would've been ive. At least it would've made this a moreA.__dict__['ive'] question ;) I'll see myself out –  Joakim  Oct 1 '15 at 15:01

4 Answers

up vote 60 down vote accepted

First of all A.__dict__.__dict__ is different from A.__dict__['__dict__'], and the former doesn't exist. The latter is the __dict__ attribute that the instances of the class would have. It's a descriptor object that returns the internal dictionary of attributes for the specific instance. In short, the __dict__attribute of an object can't be stored in object's __dict__, so it's accessed through a descriptor defined in the class.

To understand this, you'd have to read the documentation of the descriptor protocol.

The short version:

  1. For an instance of class A, access to instance.__dict__ is provided by A.__dict__['__dict__'] which is the same as vars(A)['__dict__'].
  2. For the class A, access to A.__dict__ is provided by type.__dict__['__dict__'] (in theory) which is the same as vars(type)['__dict__'].

The long version:

Both classes and objects provide access to attributes both through the attribute operator (implemented via the class or metaclass's __getattribute__), and the __dict__ attribute/protocol which is used by vars(ob).

For normal objects, the __dict__ object creates a separate dict object, which stores the attributes, and __getattribute__ first tries to access it and get the attributes from there (before attempting to look for the attribute in the class by utilizing the descriptor protocol, and before calling __getattr__). The __dict__ descriptor on the class implements the access to this dictionary.

  • x.name is equivalent to trying those in order: x.__dict__['name']type(x).name.__get__(x, type(x))type(x).name
  • x.__dict__ does the same but skips the first one for obvious reasons

As it's impossible for the __dict__ of instance to be stored in __dict__ of the instance, it is accessed through the descriptor protocol directly instead, and is stored in a special field in the instance.

A similar scenario is true for classes, although their __dict__ is a special proxy object that pretends to be a dictionary (but might not be internally), and doesn't allow you to change it or replace it with another one. This proxy allows you, among all else, to access the attributes of a class that are specific to it, and not defined in one of its bases.

By default, a vars(cls) of an empty class carries three descriptors - __dict__ for storing the attributes of the instances, __weakref__ which is used internally by weakref, and the docstring of the class. The first two might be gone if you define __slots__. Then you wouldn't have __dict__ and __weakref__ attributes, but instead you'd have a single class attribute for each slot. The attributes of the instance then wouldn't be stored in a dictionary, and access to them will be provided by the respective descriptors in the class.


And lastly, the inconsistency that A.__dict__ is different from A.__dict__['__dict__'] is because the attribute __dict__ is, by exception, never looked up in vars(A), so what is true for it isn't true for practically any other attribute you'd use. For example, A.__weakref__ is the same thing as A.__dict__['__weakref__']. If this inconsistency didn't exist, using A.__dict__ would not work, and you'd have to always use vars(A) instead.

share improve this answer
 
5 
Thanks for the detailed answer. Although I had to read it a few times, I think it has been a while since I learned so many new details of Python. –  porgarmingduod  Feb 3 '11 at 12:52

Since A.__dict__ is a dictionary storing A attributes, A.__dict__['__dict__'] is the direct reference to that same A.__dict__ attribute.

A.__dict__ contains a (kind-of) reference to itself. The "kind-of" part is why the expression A.__dict__ returns a dictproxy instead of a 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'
share improve this answer
 
5 
A.__dict__['__dict__'] isn't a reference to A.__dict__. It implements the __dict__ attribute of the instances. To try this for yourself, A.__dict__['__dict__'].__get__(A(), A) returns the attributes ofA(), while A.__dict__['__dict__'].__get__(A, type) fails. –  Rosh Oxymoron  Feb 2 '11 at 17:37

Lets do some exploring!

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

I wonder what that is?

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

What attributes does a getset_descriptor object have?

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

By making a copy of that dictproxy we can find some interesting attributes, specifically __objclass__ and __name__.

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

So __objclass__ is a reference to A and __name__ is just the string '__dict__', name of an attribute perhaps?

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

There we have it! A.__dict__['__dict__'] is an object that can refer back to A.__dict__.

share improve this answer
 
 
PEP 252 says that __objclass__ is the class that defined this attribute, not that is an attribute of that class. This makes your getattr example incorrect. A more correct one would begetattr(A().__dict__['__dict__'].__objclass__, A.__dict__['__dict__'].__name__) –  Rosh Oxymoron  Feb 2 '11 at 17:48

You can try the following simple example to understand more of this:

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

From the above example, it seems that class objects attributes are stored by their class, class's attributes are stored by their class, which are metaclasses. This is also validated by:

>>> a.__dict__ == A.__getattribute__(a, '__dict__')
True
>>> A.__dict__ == type.__getattribute__(A, '__dict__')
True
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值