Python对象__dict__方法分析札记

由Jeza Chen 发表于 May 4, 2025

该文章是对Python对象__dict__方法的分析笔记,未完待续…

1: 为什么类里面的__dict__里面有一个键为__dict__的项?

这是因为避免了通过实例调用__dict__的无限递归。对于类A的一个实例aa.__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__'],如此类推。