Python 为什么类dict是映射代理?

Python 为什么类dict是映射代理?,python,python-3.x,class,dictionary,python-internals,Python,Python 3.x,Class,Dictionary,Python Internals,我想知道为什么类\uuu dict\uuuu是一个映射代理,而实例\uu dict\uuuu只是一个普通的dict >>> class A: ... pass >>> a = A() >>> type(a.__dict__) <class 'dict'> >>> type(A.__dict__) <class 'mappingproxy'> >>A类: ... 通过 >>>a=a(

我想知道为什么类
\uuu dict\uuuu
是一个
映射代理
,而实例
\uu dict\uuuu
只是一个普通的
dict

>>> class A:
...     pass

>>> a = A()
>>> type(a.__dict__)
<class 'dict'>
>>> type(A.__dict__)
<class 'mappingproxy'>
>>A类:
...     通过
>>>a=a()
>>>类型(a.uuu dict_uuu)
>>>类型(A.uuu dict_uuu)

这有助于解释器确保类级属性和方法的键只能是字符串


在其他地方,Python是一种“同意的成人语言”,这意味着对象的dict由用户公开和可变。然而,对于类的类级属性和方法,如果我们能够保证键是字符串,我们就可以简化和加速类级属性和方法查找的通用案例代码。特别是,通过假设类dict键是字符串,简化并加速了新型类的uuu mro uuu搜索逻辑。

映射代理只是一个没有
\uu setattr\uuu
方法的dict

您可以签出并参考此代码

from types import MappingProxyType
d={'key': "value"}
m = MappingProxyType(d)
print(type(m)) # <class 'mappingproxy'>

m['key']='new' #TypeError: 'mappingproxy' object does not support item assignment
从类型导入映射ProxyType
d={'key':“value”}
m=映射ProxyType(d)
打印(类型(m))#
m['key']='new'#TypeError:'mappingproxy'对象不支持项分配

mappingproxy是从Python 3.3开始的。以下代码显示dict类型:

class C:pass
ci=C()
print(type(C.__dict__)) #<class 'mappingproxy'>
print(type(ci.__dict__)) #<class 'dict'>
C级:合格
ci=C()
打印(键入(C.uu dict_uu))#
打印(类型(ci.\uuu dict\uuuu))#

因为Python3.3
mappingproxy
类型来自
dictproxy
。在这个话题上有一个有趣的话题

要找到这种类型的文档有点困难,但方法的文档对此进行了完美的描述(尽管有一段时间):

模块和实例等对象具有可更新的 属性但是,其他对象可能对其
\uuuu dict\uuuu
属性(例如,类使用types.MappingProxyType来防止直接更新字典)

如果需要创建新的类属性,可以使用
setattr
。值得注意的是,
mappingproxy
不可JSON序列化,请查看以了解原因


这种类型的历史也是非常有趣的:

  • Python2.7:
    type(A.\u dict\u)
    返回
    type(dict())
    ,并且可以通过
    \u dict\u
    分配新属性,例如
    A.\u dict\u['foo']='bar'
  • Python3.0-3.2:
    类型(A.。\uu dict\uuu)
    返回
    ,引入了区别。尝试分配新属性会产生
    TypeError
    。需要添加
    dictproxy
    作为公共内置类型
  • Python3.3:添加了上述
    类型

出于好奇:
mappingproxy
使
类成为只读的,因此只有
class.\uuuuuuuuu setattr\uuuuu
仍然是设置类属性的一个途径,正是该方法。这也与任何重写
类型的人相关。\uuuu setattr\uuuuuuu
(希望/可能是一个非常小的集合),因为你不能写
\uuuu dict\uuuu
;您必须使用
super()
。它还确保,如果您给一个类一个新的魔术方法,Python可以更新相关的C级插槽。如果您通过使用类似于
gc.get\u referents(FooClass.\uu dict\uuuuu)[0][''eq\uuuuu']=eqmethod
的方法绕过这一点,
FooClass
的实例可能实际上不会使用
eqmethod
进行比较。我想任何人想要将派生类序列化为Json都是不幸运的:/@MartijnPieters澄清一下,这种只读特性是为什么只能通过类本身的访问来重新分配类属性的原因?为什么试图通过实例访问重新分配类属性会导致新的实例属性与类属性同名?