charlemagne
charlemagne

Reputation: 322

Simple and performant way to proxy and wrap method calls

What I'm trying to achieve is to wrap specific method calls of a class with a method of its subclass. I could do this one by one, like this:

class Subclass(ParentClass):
    def _handle_response(self, data, _suppress=None):
        # process data
        return data

    def foo(self, *a, _suppress=None, **kw):
        return self._handle_response(super().foo(*a, **kw), _suppress=_suppress)

    def bar(self, *a, _suppress=None, **kw):
        return self._handle_response(super().bar(*a, **kw), _suppress=_suppress)

But since there are ~20 methods that need to be wrapped this way, it strikes me as quite redundant. Another way I've come up with is to use __getattribute__ like this:

class Subclass(ParentClass):
    def _handle_response(self, data, _suppress=None):
        # process data
        return data

    def __getattribute__(self, attr):
        if attr in {"foo", "bar"}:
            def wrapped(_self, *a, _suppress=None, **kw):
                return self._handle_response(
                    getattr(ParentClass, attr)(_self, *a, **kw),
                    _suppress=_suppress
                )
            return wrapped
        return super().__getattribute__(attr)

This works but I'm not really happy with the fact that each time a method is called like this, a new wrapper function is created. This a lot of unnecessary overhead that I'd like to avoid. I could cache these, but again, this seems to be not a very elegant solution.

Anyone has an idea on how to approach this?

Upvotes: 1

Views: 222

Answers (2)

charlemagne
charlemagne

Reputation: 322

For anyone interested, this is how I ended up solving the problem based on zxzak's answer.

class Subclass(ParentClass):
    def __new__(cls, *args, **kwargs):
        for method in ("foo", "bar", ...):
            def make_wrapper(m):
                def wrap(self, *a, _suppress=None, **kw):
                    return self._handle_response(getattr(super(type(self), self), m)(*a, **kw), _suppress=_suppress)
                return wrap
            setattr(cls, method, make_wrapper(m=method))
        return super().__new__(cls, *args, **kwargs)

Upvotes: 0

zxzak
zxzak

Reputation: 9446

Classes are created during runtime so you can still modify them after the declaration and add the methods you want.

class ParentClass():
    def foo(self, *a, _suppress=None, **kw):
        return "foo"

    def bar(self, *a, _suppress=None, **kw):
        return "bar"

class Subclass(ParentClass):
    def _handle_response(self, data, _suppress=None):
        return data + " plus"

methods_to_wrap = ["foo", "bar"]

for method in methods_to_wrap:
    def make_wrapper(m):
        def wrap(self, *a, _suppress=None, **kw):
            return self._handle_response(getattr(super(type(self), self), m)(*a, **kw), _suppress=_suppress)
        return wrap
    setattr(Subclass, method, make_wrapper(m=method))

o = Subclass()
print(o.foo())
print(o.bar())

prints

foo plus
bar plus

The reason I am using the make_wrapper function is to avoid late binding. The rest should be self-explanatory.

Upvotes: 1

Related Questions