Python类的__dict__.__dict__属性是什么?
>>> 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__
里,什么时候会有东西在里面呢?
3 个回答
让我们来探索一下吧!
>>> 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__
的对象。
你可以试试下面这个简单的例子,来更好地理解这个概念:
>>> 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
首先,A.__dict__.__dict__
和 A.__dict__['__dict__']
是不一样的。前者是不存在的,而后者是类实例所拥有的 __dict__
属性。这个属性是一个数据描述符对象,它返回特定实例的内部属性字典。简单来说,对象的 __dict__
属性不能存储在对象的 __dict__
中,所以它是通过类中定义的描述符来访问的。
要理解这一点,你需要阅读 描述符协议的文档。
简而言之:
- 对于类
A
的实例a
,访问a.__dict__
是通过A.__dict__['__dict__']
来实现的,这和vars(A)['__dict__']
是一样的。 - 对于类
A
,访问A.__dict__
是通过type.__dict__['__dict__']
(理论上)来实现的,这和vars(type)['__dict__']
是一样的。
详细版本:
类和对象都可以通过属性操作符(通过类或元类的 __getattribute__
实现)以及 __dict__
属性/协议(由 vars(ob)
使用)来访问属性。
对于普通对象,__dict__
对象会创建一个单独的 dict
对象,用来存储属性,而 __getattribute__
首先会尝试访问这个字典并从中获取属性(在尝试通过描述符协议在类中查找属性之前,以及在调用 __getattr__
之前)。类中的 __dict__
描述符实现了对这个字典的访问。
a.name
的访问顺序相当于尝试以下几种方式:type(a).__dict__['name'].__get__(a, type(a))
(仅当type(a).__dict__['name']
是一个 数据 描述符时),a.__dict__['name']
,type(a).__dict__['name'].__get__(a, type(a))
,type(a).__dict__['name']
。a.__dict__
也是这样做,但显然跳过了第二步。
由于实例的 __dict__
不可能存储在自身中,因此它是通过描述符协议直接访问的,并存储在实例的一个特殊字段中。
类的情况也是类似,尽管它们的 __dict__
是一个特殊的代理对象,假装是一个字典(但内部可能不是),并且不允许你更改或替换它。这个代理允许你访问类特有的属性,而这些属性并没有在其基类中定义。
默认情况下,一个空类的 vars(cls)
包含三个描述符:__dict__
用于存储实例的属性,__weakref__
是 weakref
内部使用的,__doc__
是类的文档字符串。如果你定义了 __slots__
,前两个可能会消失。那样的话,你就不会有 __dict__
和 __weakref__
属性,而是会为每个槽定义一个单独的类属性。实例的属性将不会存储在字典中,而是通过类中的相应描述符来访问。
最后,A.__dict__
和 A.__dict__['__dict__']
不同的原因是,属性 __dict__
是一个例外,它永远不会在 vars(A)
中查找,因此对它的规则和你使用的其他属性并不相同。例如,A.__weakref__
和 A.__dict__['__weakref__']
是一样的。如果没有这种不一致,使用 A.__dict__
将无法工作,你将不得不始终使用 vars(A)
。