拦截对象属性上的 __getitem__ 调用

Mr.*_*ius 5 python oop

问题:如何拦截__getitem__对对象属性的调用?

解释:

所以,场景如下。我有一个对象将类似 dict 的对象存储为属性。每次__getitem__调用此属性的方法时,我都想拦截该调用并根据键对获取的项目进行一些特殊处理。我想要的看起来像这样:

class Test:

    def __init__(self):
        self._d = {'a': 1, 'b': 2}

    @property
    def d(self, key):
        val = self._d[key]
        if key == 'a':
            val += 2
        return val
t = Test()
assert(t.d['a'] == 3) # Should not throw AssertionError
Run Code Online (Sandbox Code Playgroud)

问题是 @property 方法实际上无法访问__getitem__调用中的密钥,所以我根本无法检查它来执行我的特殊后处理步骤。

重要说明:我不能只是对 MutableMapping 进行子类化,覆盖__getitem__我子类的方法来执行此特殊处理,并将子类的实例存储在self._d. 在我的实际代码self._d中已经是 MutableMapping 的子类,这个子类的其他客户端需要访问未修改的数据。

感谢您的任何帮助!

Sha*_*ger 4

一种解决方案是Mapping代理底层映射。该d属性会将底层self._d映射包装在代理包装器中并返回它,并且使用该代理将表现出必要的行为。例子:

from collections.abc import Mapping

class DProxy(Mapping):
    __slots__ = ('proxymap',)
    def __init__(self, proxymap):
        self.proxymap = proxymap
    def __getitem__(self, key):
        val = self.proxymap[key]
        if key == 'a':
            val += 2
        return val
    def __iter__(self):
        return iter(self.proxymap)
    def __len__(self):
        return len(self.proxymap)
Run Code Online (Sandbox Code Playgroud)

一旦你做到了,你原来的课程可以是:

class Test:
    def __init__(self):
        self._d = {'a': 1, 'b': 2}

    @property
    def d(self):
        return DProxy(self._d)
Run Code Online (Sandbox Code Playgroud)

然后用户将访问Testwith的实例test.d[somekey];将返回代理,然后代理将根据需要test.d修改 的结果。他们甚至可以存储引用然后使用,同时保留必要的代理行为。如果需要,您可以将其设为 a ,但是当目标是读取值而不通过代理修改它们时,基于普通的代理可以避免复杂性。__getitem__somekeylocald = test.dlocaldMutableMappingMapping

是的,这会DProxy在每次访问时创建一个新实例d;如果您愿意,可以缓存它,但考虑到DProxy类的简单性,只有在最热的代码路径上频繁执行__init__通过属性的合格访问时,成本才有意义。d