use python __call__ magic method to replicate `def` within class

55 Views Asked by At

Suppose we have a class which acts as a decorator

class Foo:
    def __init__(self, func):
        self.func = func
        self.variable1 = 1
        self.variable2 = 2
    def __call__(self, *args, **kwargs):
        return self.func(*args, **kwargs)

It cannot be readily used on an instance method

class Bar:
    @Foo
    def dance(self):
        return 0
Bar().dance()  # TypeError: missing 1 required positional argument: 'self'

Is it possible to make dance() behave just like a built-in boundmethod, but also allow it to have additional variables like Bar().dance.variable1?

A related question: we know the undecorated Bar().__dance__ has attribute __self__ pointing to the Bar object. Is it possible to make use of __self__ in variable1?

1

There are 1 best solutions below

0
chepner On

Foo needs to be a descriptor, with a __get__ method similar to that defined by function. Adapted from https://docs.python.org/3/howto/descriptor.html#functions-and-methods:

class Foo:
    def __init__(self, func):
        self.func = func
        self.variable1 = 1
        self.variable2 = 2

    def __call__(self, *args, **kwargs):
        return self.func(*args, **kwargs)

    def __get__(self, obj, objtype):
        if obj is None:
            return self
        return types.MethodType(self, obj)

The __self__ attribute you mentioned is an attribute of the MethodType value returned by __get__, not of dance. (This is because Bar().dance does not evaluate to the function dance, but to the return value of the __get__ method.)

It's probably simpler to just not use a class as a decorator, and use a function (which is already a descriptor) instead.

def Foo(f):
    def _(*args, **kwargs):
        return f(*args, **kwargs)
    return _