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__
中发现的这个<attribute '__dict__' of 'A' objects>
是什么,它什么时候包含什么?
【问题讨论】:
更合适的示例变量应该是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__ 属性是啥?的主要内容,如果未能解决你的问题,请参考以下文章