Python类的__dict__.__dict__属性是什么?

104 投票
3 回答
91614 浏览
提问于 2025-04-16 11:06
>>> 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 个回答

10

让我们来探索一下吧!

>>> 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__ 的对象。

11

你可以试试下面这个简单的例子,来更好地理解这个概念:

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

首先,A.__dict__.__dict__A.__dict__['__dict__'] 是不一样的。前者是不存在的,而后者是类实例所拥有的 __dict__ 属性。这个属性是一个数据描述符对象,它返回特定实例的内部属性字典。简单来说,对象的 __dict__ 属性不能存储在对象的 __dict__ 中,所以它是通过类中定义的描述符来访问的。

要理解这一点,你需要阅读 描述符协议的文档

简而言之:

  1. 对于类 A 的实例 a,访问 a.__dict__ 是通过 A.__dict__['__dict__'] 来实现的,这和 vars(A)['__dict__'] 是一样的。
  2. 对于类 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)

撰写回答