拦截对象属性的__getitem__调用

时间:2018-07-03 16:55:12

标签: 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

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

重要说明:我不能只是将MutableMapping子类化,重写子类的__getitem__方法以进行特殊处理,然后将子类的实例存储在self._d中。在我的实际代码中,self._d已经是MutableMapping的子类,并且该子类的其他客户端需要访问未修改的数据。

感谢所有帮助!

3 个答案:

答案 0 :(得分:3)

一种解决方案是使用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)

完成该操作后,您的原始课程可以是:

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

    @property
    def d(self):
        return DProxy(self._d)

然后,用户将使用Test访问test.d[somekey]的实例; test.d将返回代理,该代理随后将根据__getitem__的需要修改somekey的结果。他们甚至可以用locald = test.d存储引用,然后使用locald保留必要的代理行为。您可以根据需要将其设置为MutableMapping,但是当目标正在读取值而从不通过代理对其进行修改时,基于普通Mapping的代理可以避免复杂性。

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

答案 1 :(得分:0)

这与ShadowRanger的方法非常相似。它更短一些,因为它直接从dict继承,因此要定义的委托较少。

class DictProxy(dict):
    def __getitem__(self, item):
        val = super().__getitem__(item)
        if item == 'a':
            val += 2
        return val

class Test:

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

    @property
    def d(self):
        return DictProxy(self._d)

t = Test()
assert(t.d['a'] == 3) # Does not throw AssertionError anymore :)

就行为而言,它的确归根结底。两种方法都没错。

编辑:感谢ShadowRanger指出此解决方案实际上每次都会复制字典。因此,最好使用他的显式委派解决方案,该解决方案使用相同的内部字典表示形式。这样会更高效,而且,如果您将来想更改代理以使其实际上影响原始数据结构,那么他的方法将使将来的更改变得容易得多。

答案 2 :(得分:0)

没有浅表复制,最短的修改可能性:

from collections import UserDict

class DictProxy(UserDict):
    def __init__(self, d):
        self.data = d

    def __getitem__(self, item):
        val = super().__getitem__(item)
        if item == 'a':
            val += 2
        return val