0

I want a decorator that I can use as both @decorator and decorator() with class methods, like this :

def decorator_with_args(name):
    print(f'Hello {name} !')
    def decorator(func):
        def wrapper(self, *args, **kwargs):
            print(f'Hello {self.title} {name} again !!')
            print(f"Calling {func.__name__} with instance {self}...")
            result = func(self, *args, **kwargs)
            print(f"{func.__name__} finished. Result: {result}")
            return result
        return wrapper
    return decorator

class Calculator:
    def __init__(self):
        self.title = 'Mr'

    @decorator_with_args('World')
    def add(self, a, b):
        return a + b

    def add2(self, a, b):
        return a + b

    def do_add(self, a, b):
        return decorator_with_args(f'World{a}')(self.add2)(self, a, b)

# Usage 1
calc = Calculator()
result = calc.add(3, 5)

# Usage 2
calc = Calculator()
result = calc.do_add(3, 5)

The reason why I want to use the decorator function as the above two representations is because:

  1. I want to use @decorator_with_args when the argument is known such as @decorator_with_args('World')
  2. I want to use decorator_with_args() when the argument is not known and I want the argument to be dynamic such as decorator_with_args(f'World{a}')

While @decorator_with_args works as expected [Usage 1], I get an error with decorator_with_args() [Usage 2]. I tried few things, but none of them worked for the latter.

If I try to pass (self, a, b), for example decorator_with_args(f'World{a}')(self.add2)(self, a, b), I get TypeError: add2() takes 3 positional arguments but 4 were given.

On the other hand, if I try to pass (a, b) without self, for example decorator_with_args(f'World{a}')(self.add2)(a, b), I get AttributeError: 'int' object has no attribute 'title'.

I appreciate there may exist other similar questions, which I tried to search but could not get them to work for my use case.

2 Answers 2

4

In order to replicate the standard decorating of instance method, you need to decorate the underlying function that is bound to that method, not the method itself - that is because self.add2 implicitly adds self as first argument to that function, so it gets doubled.

    def do_add(self, a, b):
        return decorator_with_args(f'World{a}')(Calculator.add2)(self, a, b)

or

    def do_add(self, a, b):
        return decorator_with_args(f'World{a}')(self.add2.__func__)(self, a, b)
Sign up to request clarification or add additional context in comments.

Comments

0

If you add print(f"Calling {func} with instance {self}...") in your wrapper, you will see the following:

# Usage 1
calc = Calculator()
result = calc.add(3, 5)
>>> Calling <function Calculator.add at 0x15815b5b0> with instance <__main__.Calculator object at 0x158174430>...
>>> add finished. Result: 8
# Usage 2
calc = Calculator()
result = calc.do_add(3, 5)
>>> Calling <bound method Calculator.add2 of <__main__.Calculator object at 0x158177100>> with instance <__main__.Calculator object at 0x158177100>...
>>> TypeError...

This is because self.add2 is bound to the instance and so your wrapper will have 4 arguments:

  • the "self" from the bound method
  • the "self" from the function call in do_add
  • a
  • b

If you want to access the function from the bound method, you can do the following:

def do_add(self, a, b):
    return decorator_with_args(f'World{a}')(Calculator.add2)(self, a, b)
>>> Hello World3 !
>>> Hello Mr World3 again !!
>>> Calling <function Calculator.add2 at 0x15815b370> with instance <__main__.Calculator object at 0x1581be860>...
>>> <__main__.Calculator object at 0x1581be860> 3 5
>>> add2 finished. Result: 8

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.