Python中如何实现不可变字典inmutabledict

python inmutabledict 的实现

关于在 python 中如何实现不可变字典的方法。早在 pep416 中,就建议 python 官方实现 inmutabledict,但是官方否认了。理由主要是

* 根据 Raymond Hettinger 的说法,使用 frozendict 很愚蠢。 那些使用它的人倾向于仅将它用作提示,例如声明全局或类级别的“常量”:它们实际上不是永久不变的,因为任何人仍然可以指定名称。
* There are existing idioms for avoiding mutable default values.


所以,这个提议就被否决了。但是我们依旧可以自己实现一个 inmutabledict。inmutable 主要的特点是

* dict 内的值只能在初始化的时候指定
* 在运行期间,不能添加删除新增 dict 内部的值

结合[starkoverflow 上面的回答]( https://stackoverflow.com/questions/2703599/what-would-a-frozen-dict-be)
我们可以通过如下几种魔改的方式实现 python 的 inmutabledict

### 几种变通的方案
#### 1. 最原始的方法,修改 setitem 魔术方法
在 python 中,d[“foo”]=bar,将 foo 和 bar 作为参数,调用的是 python 的模式方法__setitem__。函数原型为def __setitem__(self, key, value):。所以,我们可以继承 dict 类,实现自己的__setitem__。在修改值的时候,抛出 TypeError。不就是可以达到无法修改字典的值的目的了嘛。代码如下

<br>class myDict(dict):<br> def __setitem__(self, key, value):<br> raise TypeError("inmutabledict can not be modifyed value")<br><br>d = myDict({1:2,3:4})<br>d[1]=4<br><br>
运行则会提示

<br> raise TypeError("inmutabledict can not be modifyed value")<br>TypeError: inmutabledict can not be modifyed value<br><br>Process finished with exit code 1<br>
很好,完美的完成了任务。这种方法应用最为广泛,在 werkzeug 框架中的 ImmutableDict 等,就是通过修改魔术方法来实现的不可变字典类型。

但是在 pep0416 中,还提到了几种其他方法,PyDictProxy_New 等。下面来试一下






#### 2. [pythonapi.PyDictProxy_New]( http://code.activestate.com/recipes/576540/)

在官方介绍 capi 的 PyDictProxy_New 中,使用代理模式,代理使用字典。并且拦截了字典的修改请求。介绍如下

<br>PyObject* PyDictProxy_New(PyObject *mapping)¶<br>Return value: New reference.<br>Return a types.MappingProxyType object for a mapping which enforces read-only behavior. This is normally used to create a view to prevent modification of the dictionary for non-dynamic class types.<br>
意思就是你传入个 dict,这个函数返回一个 dict (其实是 types.MappingProxyType ),然后这个返回的 dict 就不可以修改啦。是不是很简单,代码实现如下

<br><br>#!/usr/bin/env python<br># -*- coding: UTF-8 -*-<br><br><br>from ctypes import pythonapi, py_object<br><br>PyDictProxy_New = pythonapi.PyDictProxy_New<br>PyDictProxy_New.argtypes = (py_object,)<br>PyDictProxy_New.restype = py_object<br><br>def make_dictproxy(obj):<br> assert isinstance(obj, dict)<br> return pythonapi.PyDictProxy_New(obj)<br>a={'a': 'b', 'c': 'd'}<br>d = make_dictproxy(a)<br>

这是如果修改的话,则会提示 TypeError: ‘mappingproxy’ object does not support item assignment。同样达到了要求。这种方法的弊端主要在于依赖特定的平台,只能适用于 cpython。而上面那种则适用于所有平台,cpython,pypy 等。




#### 3 .class types.MappingProxyType(mapping)
这种方法其实于 PyDictProxy_New 一样,只不过在 py3.3 中才实现。
代码如下

<br>from types import MappingProxyType<br><br>def make_dictproxy(obj):<br> assert isinstance(obj, dict)<br> return MappingProxyType(obj)<br>
Python中如何实现不可变字典inmutabledict


5 回复

建议考虑一下
from UserDict import UserDict # py2
from collections import UserDict # py3

class MyDict(UserDict)


在Python里,要搞一个真正的不可变字典,标准库types模块里的MappingProxyType是首选。这玩意儿给你一个原字典的只读视图,动不了原数据,但能反映它的变化。

from types import MappingProxyType

# 先弄个普通字典当底层存储
writable_dict = {'a': 1, 'b': 2}
# 用MappingProxyType包起来,得到只读视图
read_only_dict = MappingProxyType(writable_dict)

print(read_only_dict['a'])  # 能读,输出: 1

# 下面这些操作都会抛TypeError,因为改不了
try:
    read_only_dict['c'] = 3
except TypeError as e:
    print(f"赋值报错: {e}")

try:
    del read_only_dict['a']
except TypeError as e:
    print(f"删除报错: {e}")

# 但底层字典变了,视图会跟着变
writable_dict['c'] = 3
print(read_only_dict)  # 输出: {'a': 1, 'b': 2, 'c': 3}

它的核心是只读代理,不是拷贝数据。想要完全独立且内容固定的,可以用frozendict第三方库,或者拿tupledataclassfrozen=True)自己封装一个。

总结:要动态只读视图用MappingProxyType,要完全冻结的数据用frozendict或不可变结构。

from types import MappingProxyType 可以将字典封装为只读

或者

继承 UserDict,实现 setitem 方法

😂我也是 MappingProxyType



哈哈,巧了,昨晚看《流畅的 Python 》 刚好看到。

回到顶部