ImmutableDict 实现不调用 __setitem__

问题描述 投票:0回答:1

在下面的代码中,我创建了一个类

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-3.x dictionary generics multiple-inheritance magic-methods
1个回答
0
投票

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 中一直是可能的。

© www.soinside.com 2019 - 2024. All rights reserved.