该文章是对Python对象__dict__
方法的分析笔记,未完待续…
1: 为什么类里面的__dict__
里面有一个键为__dict__
的项?
这是因为避免了通过实例调用__dict__
的无限递归。对于类A
的一个实例a
,a.__dict__
存储了实例a
的属性,而__dict__
本身也作为了a
的属性。当调用a.__dict__
时,根据实例调用属性规则,如果A.__dict__['__dict__']
不存在,或者不是一个数据描述器,那么会继而调用a.__dict__['__dict__']
,从而陷入无限递归。
>>> a.__dict__
{'a': 8}
>>> A.__dict__
mappingproxy({'__module__': '__main__', 'a': 5, '__init__': <function A.__init__ at 0x0000022E13EA9DA0>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None})
>>> A.__dict__['__dict__']
<attribute '__dict__' of 'A' objects>
>>> type(A.__dict__['__dict__'])
<class 'getset_descriptor'>
>>> A.__dict__['__dict__'].__get__(a, A)
{'a': 8}
因此,Python在类中的__dict__
添加了一个键为__dict__
的项,其为一个数据描述器(getset_descriptor
,可参见CPython的源码),当通过实例调用__dict__
(即a.__dict__
)时,解释器会优先调用这个数据描述器,从而避免了无限递归。当然,如果要覆写实例的__dict__
属性(比如a.__dict__ = {}
),也是会优先调用这个数据描述器实现覆盖,而不会造成a.__dict__['__dict__'] = {}
这种情况。(问题来了:那实例的__dict__
字典放在哪里了呢?)
如果我想通过改写类的__dict__
属性,删掉里面的__dict__
项,是否会造成无限递归呢?不会的,因为类的__dict__
属性是一个mappingproxy
对象,其本质上是一个只读的字典视图,因此无法直接修改,保证了程序的安全性。
>>> del A.__dict__['__dict__']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'mappingproxy' object does not support item deletion
2. 类中的__dict__
和实例中的__dict__
有什么区别?
类中的__dict__
是一个mappingproxy
对象,实例中的__dict__
是一个普通的字典对象。类中的__dict__
是只读的,而实例中的__dict__
是可读可写的。本质上,类的__dict__
其实就是其超类的__dict__['__dict__']
,如此类推。