问题:如何截获__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的子类,并且该子类的其他客户端需要访问未修改的数据。
感谢所有帮助!
答案 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