Python 类的 __dict__.__dict__ 属性是啥?

Posted

技术标签:

【中文标题】Python 类的 __dict__.__dict__ 属性是啥?【英文标题】:What is the __dict__.__dict__ attribute of a Python class?Python 类的 __dict__.__dict__ 属性是什么? 【发布时间】:2011-06-20 02:56:27 【问题描述】:
>>> 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__。在A.__dict__.__dict__ 中发现的这个&lt;attribute '__dict__' of 'A' objects&gt;什么,它什么时候包含什么?

【问题讨论】:

更合适的示例变量应该是ive。至少它会使这成为一个更多A.__dict__['ive'] 的问题;)我会看到自己出局 【参考方案1】:

首先A.__dict__.__dict__ 不同于A.__dict__['__dict__']。前者不存在,后者是类实例将具有的__dict__ 属性。它是一个数据描述符对象,它返回特定实例的内部属性字典。总之,对象的__dict__属性不能存储在对象的__dict__中,所以通过类中定义的描述符来访问。

要理解这一点,您必须阅读documentation of the descriptor protocol。

简短版:

    对于A 类的实例a,对a.__dict__ 的访问由A.__dict__['__dict__'] 提供,与vars(A)['__dict__'] 相同。 对于A 类,A.__dict__ 的访问权限由type.__dict__['__dict__'] 提供(理论上)与vars(type)['__dict__'] 相同。

长版:

类和对象都通过属性运算符(通过类或元类的__getattribute__ 实现)和vars(ob) 使用的__dict__ 属性/协议提供对属性的访问。

对于普通对象,__dict__ 对象会创建一个单独的 dict 对象来存储属性,__getattribute__ 首先尝试访问它并从那里获取属性(在尝试在类通过使用描述符协议,在调用__getattr__之前)。类上的__dict__ 描述符实现了对这个字典的访问。

a.name 相当于按顺序尝试:type(a).__dict__['name'].__get__(a, type(a))(仅当 type(a).__dict__['name']data 描述符时)、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)

【讨论】:

感谢您的详细回答。虽然我不得不读了几遍,但我想我已经有一段时间没有学到这么多 Python 的新细节了。 为什么不能将对象的__dict__属性存储在对象的__dict__中? @zumgruenenbaum 由于__dict__ 旨在存储所有实例属性,因此最终会在对象的__dict__ 上查找obj.x 形式的属性访问,即obj.__dict__['x']。现在,如果__dict__ 没有作为描述符实现,这将导致无限递归,因为要访问obj.__dict__,您需要将其查找为obj.__dict__['__dict__']。描述符规避了这个问题。【参考方案2】:

让我们来探索一下吧!

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

【讨论】:

PEP 252 说__objclass__定义此属性的类,而不是该类的属性。这使您的 getattr 示例不正确。更正确的是getattr(A().__dict__['__dict__'].__objclass__, A.__dict__['__dict__'].__name__) @RoshOxymoron 你的表情提高了KeyError: '__dict__',与@AndrewClark 的相反。【参考方案3】:

您可以尝试以下简单示例来了解更多信息:

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

【讨论】:

奇怪的是,如果在第二次比较中将is替换为==,即A.__dict__ is type.__dict__['__dict__'].__get__(A),那么python 2.7.15+和3.6.8的结果都是False @ArneVogel 这是因为表达式 A.__dict__(或 type.__dict__['__dict__'].__get__(A))的计算结果为 new types.MappingProxyType 实例:A.__dict__ is not A.__dict__。 (有关此类型历史的更多信息here。)与表达式a.__dict__(或A.__dict__['__dict__'].__get__(a))相反,其计算结果为相同的dict 实例:a.__dict__ is a.__dict__

以上是关于Python 类的 __dict__.__dict__ 属性是啥?的主要内容,如果未能解决你的问题,请参考以下文章

如何访问 Python 超类的属性,例如通过 __class__.__dict__?

python __dict__ 跟 dir()的区别

python中dir(),__dict__

Python_68类的特殊成员之dict

python __dict__

Python 查看对象属性的几种方式: __dict__, dir(), vars(), locals()