在下面的代码中,我创建了一个类
ImmutableDict
,它似乎有一个 __setitem__
实现,应该抛出异常,但事实并非如此。为什么不呢?
from typing import Mapping, TypeVar
KT = TypeVar('KT')
VT_co = TypeVar('VT_co', covariant=True)
class Immutable:
def __init__(self, *args, **kwargs):
super(Immutable, self).__init__(*args, **kwargs)
# set them all to self.raise_immutable
self.__setitem__ = self.__delitem__ = self.setdefault = self.update = self.pop = self.popitem = self.clear = self.raise_immutable
def raise_immutable(self, *args, **kwargs):
clsname = self.__class__.__name__
raise TypeError(f"{clsname} objects are immutable")
def __repr__(self):
clsname = self.__class__.__name__
content = super(Immutable, self).__repr__()
return f"{clsname}({content}"
class ImmutableDict(Immutable, dict, Mapping[KT, VT_co]):
def __hash__(self):
return hash(tuple(self.items()))
i = ImmutableDict({'a':1})
print(i) # ImmutableDict({'a': 1})
print(i.__setitem__) # <bound method Immutable.raise_immutable of ImmutableDict({'a': 1})>
# i.__setitem__('b', 2) # this throws an exception as desired
i['b'] = 2 # but yet this works?
print(i) # ImmutableDict({'a': 1, 'b': 2})
Python“神奇”方法,即语言内部使用的带有两个下划线的方法,如
__setitem__
等......都意味着在类中实现 - 如果在实例中实现,则被忽略。
在这种情况下,您可以在运行
__init__
后隐藏实例中的方法,方法是将 self.__setitem__
分配给引发错误的方法:尽管尝试直接调用该方法也可以,如下所示:
a = ImmutableDict({"b": 1})
a.__setitem__("c", 2) # this raises
使用
[ ]
运算符时,语言将经过 (*) ImmutableDict.__setitem__
- 没有改变。
但是,实现所有变异方法并测试它们 字典状态,虽然是一种可以在此光学下工作的策略,但会遇到第二堵墙:当使用像
dict
、list
等本机类时......该语言有时可能会直接使用内部本机 API,绕过用户实施的方法。请注意,这种方法在大多数情况下都可以工作,但在边缘情况下仍然会失败:在本机代码中实现的某些函数可以直接通过字典的内部数据结构,而不是通过具有不变性保护的 __setitem__
。
对于这些用例,安全的做法是实现纯 Python 映射,要么扩展
collections.UserDict
,要么通过实现 collections.abc.MutableMapping
中的抽象方法。
请注意,不变性检查仍然必须在类方法中执行,仅替换实例中的方法仍然行不通
from collections import UserDict
from functools import wraps
def mutable_guard_decorator(method):
@wraps(method)
def mutable_guard(self, *args, **kw):
if self._immutable:
clsname = self.__class__.__name__
raise TypeError(f"{clsname} objects are immutable")
return method(*args, **kw)
return mutable_guard
class ImmutableDict(UserDict):
_immutable = False
# some code in the class body to decorate all relevant methods:
namespace = locals()
for method in [
'__delitem__', '__init__', '__setitem__',
'clear', 'fromkeys', 'pop', 'setdefault', 'update',
]:
locals[method] = mutable_guard_decorator(getattr(UserDict, method))
del namespace, method
def __init__(self, *args, **kw):
super().__init__(*args, **kw)
self._immutable = True # sets the flag on the instance, shadowing the class `False` value for it.
def __hash__(self):
return hash(tuple(self.items()))
我将把正确的类型标记保留为“家庭作业”——不确定静态类型工具能够如何很好地处理这样的元编程,而这在 Python 中一直是可能的。