Skip to content Skip to sidebar Skip to footer

Intercepting __getitem__ Calls On An Object Attribute

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 attrib

Solution 1:

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

classDProxy(Mapping):
    __slots__ = ('proxymap',)
    def__init__(self, proxymap):
        self.proxymap = proxymap
    def__getitem__(self, key):
        val = self.proxymap[key]
        if key == 'a':
            val += 2return val
    def__iter__(self):
        returniter(self.proxymap)
    def__len__(self):
        returnlen(self.proxymap)

Once you've made that, your original class can be:

classTest:def__init__(self):
        self._d = {'a': 1, 'b': 2}

    @propertydefd(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.

Solution 2:

Here's a fairly similar approach to ShadowRanger's. It's a bit shorter, as it inherits from dict directly, so there's less explicit delegation to define.

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

classTest:def__init__(self):
        self._d = {'a': 1, 'b': 2}

    @propertydefd(self):
        return DictProxy(self._d)

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

In terms of behavior, it really comes down to taste. There's nothing wrong with either approach.

EDIT: Thanks to ShadowRanger for pointing out that this solution actually copies the dictionary every time. Therefore, it's probably better to use his explicit delegation solution, which uses the same internal dictionary representation. It'll be more efficient that way, and if you ever want to change your proxy in the future so that it actually affects the original data structure, his approach will make it a lot easier to make those future changes.

Solution 3:

No shallow copying, shortest, and with modification possibilities:

from collections import UserDict

classDictProxy(UserDict):
    def__init__(self, d):
        self.data = d

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

Post a Comment for "Intercepting __getitem__ Calls On An Object Attribute"