5

The following minimal example of a decorator on a member function:

def wrap_function(func):
    def wrapper(*args, **kwargs):
        print(args)
        print(kwargs)
    return wrapper

class Foo:
    @wrap_function
    def mem_fun(self, msg):
        pass

foo = Foo()
foo.mem_fun('hi')

outputs:

(<__main__.Foo object at 0x7fb294939898>, 'hi')
{}

So self is one of the args.

However when using a wrapper class:

class WrappedFunction:
    def __init__(self, func):
        self._func = func

    def __call__(self, *args, **kwargs):
        print(args)
        print(kwargs)

def wrap_function(func):
    return WrappedFunction(func)

class Foo:
    @wrap_function
    def mem_fun(self, msg):
        pass

foo = Foo()
foo.mem_fun('hi')

the output is:

('hi',)
{}

So the self, that references the Foo object, is not accessible in the body of __call__ of the WrappedFunction object.

How can I make it accessible there?

Tobias Hermann
  • 9,936
  • 6
  • 61
  • 134

2 Answers2

2

You're losing the reference to your bounded instance by wrapping the function logic (but not the instance) and redirecting it to a class instance - at that point, the class instance's own self applies instead of the wrapped instance method as it gets lost in the intermediary decorator (wrap_function()).

You either have to wrap the call to the wrapped function and pass *args/**kwargs to it, or just make a proper wrapper class instead of adding an intermediary wrapper:

class WrappedFunction(object):

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            print(args)
            print(kwargs)
            # NOTE: `WrappedFunction` instance is available in `self`
        return wrapper

class Foo:
    @WrappedFunction()  # wrap directly, without an intermediary
    def mem_fun(self, msg):
        pass

foo = Foo()
foo.mem_fun('hi')
# (<__main__.Foo object at 0x000001A2216CDBA8>, 'hi')
# {}
zwer
  • 24,943
  • 3
  • 48
  • 66
1

Sadly, but this might be the only solution as you need it in the __call__ function. Would suggest checking this out: What is the difference between __init__ and __call__ in Python?

def wrap_function(func):
    def wrapper(*args, **kwargs):
        x = WrappedFunction(func)
        x(*args, **kwargs)
    return wrapper
Adelina
  • 10,915
  • 1
  • 38
  • 46