Question: How can I Intercept __getitem__
calls on an object attribute?
Explanation:
So, the scenario is the following. I have an object that stores a dict-like object as an attribute. Every time the __getitem__
method of this attribute gets called, I want to intercept that call and do some special processing on the fetched item depending on the key. What I want would look something like this:
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
The problem is that the @property method doesn't actually have access to the key in the __getitem__
call, so I can't check for it at all to do my special postprocessing step.
Important Note: I can't just subclass a MutableMapping, override the __getitem__
method of my subclass to do this special processing, and store an instance of the subclass in self._d
. In my actual code self._d
is already a subclass of MutableMapping and other clients of this subclass need access to the unmodified data.
Thanks for any and all help!
One solution would be a Mapping
that proxies the underlying mapping. The d
property would wrap the underlying self._d
mapping in the proxy wrapper and return it, and use of that proxy would exhibit the necessary behaviors. Example:
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)
Once you've made that, your original class can be:
class Test:
def __init__(self):
self._d = {'a': 1, 'b': 2}
@property
def d(self):
return DProxy(self._d)
Users would then access instances of Test
with test.d[somekey]
; test.d
would return the proxy, which would then modify the result of __getitem__
as needed for somekey
. They could even store off references with locald = test.d
and then use locald
while preserving the necessary proxy behaviors. You can make it a MutableMapping
if needed, but a plain Mapping
-based proxy avoids complexity when the goal is reading the values, never modifying them through the proxy.
Yes, this makes a new DProxy
instance on each access to d
; you could cache it if you like, but given how simple the DProxy
class's __init__
is, the cost is only meaningful if qualified access via the d
attribute is performed frequently on the hottest of code paths.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With