Passing arguments to a decorator in a class

I’m using argparse to instantiate a class, the class itself has a decorator I’m using that accept arguments, the problem is that I can’t use arguments from the __init__() method, because I get a NameError. Class level arguments work.

Is there any way I can pass arguments to a decorator? I don’t want to hardcode it.

Suppose you have a decorator add that would add a given argument to the returning value of the decorated function:

def add(addition):
    def decorator(func):
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs) + addition
        return wrapper
    return decorator

And you want to decorate the multiply method with add passed with self.n:

class Foo:
    def __init__(self, n):
        self.n = n

    @add(self.n) # does not work because self does not exist in class definition
    def multiply(self, multiplier):
        return self.n * multiplier

You can instead create an ad-hoc decorator with a wrapper that decorates the method on the fly when the wrapper is called, at which point self becomes available for the instance variable self.n to be passed to add:

class Foo:
    def __init__(self, n):
        self.n = n

    @(
        lambda method:
            lambda self, *args, **kwargs:
                add(self.n)(method)(self, *args, **kwargs)
    )
    def multiply(self, multiplier):
        return self.n * multiplier

print(Foo(5).multiply(2)) # outputs 15 because 5 * 2 + 5 == 15

Demo here

1 Like

It may be that Ben’s suggestion solves your problem, but can you explain
it a bit more for me? Can you for example show your whole incantation,
including how you’d hoped to write the decorator use?

I’m imagining your root problem is that when you’re defining a class the
variables are not yet bound to the class - they’re being defined in a
distinct namespace which is then gathered up and turned into a class
afterwards. Which is probably why it can’t see your variable you want to
pass to the decorator.

But I’m just guessing without seeing the code.

This does exactly what I wanted, but was quite tricky to wrap my head around at first, although I see how it works. Unfortunately, I think there is a bit too much magic involved for me to able to use this with peace of mind.

My code is essentially identical to Ben’s first example, I will repeat the same with some comments for clarification:

def add(addition):
    def decorator(func):
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs) + addition
        return wrapper
    return decorator

class Foo:
    def __init__(self, n):
        self.n = n

    # Works with hardcoded value @add(5)
    # but @add(self.n) raises NameError.
    # The goal is to use an instance variable or 
    # an equivalent as an argument to this decorator.
    # It has to be mutable at instance creation.
    #
    @add(self.n) # NameError!
    def multiply(self, multiplier):
        return self.n * multiplier


obj = Foo(n=5)

# Multiply 5 by 2
# @add(n) will add `n` to the result
result = obj.multiply(2)

print(result) # Result is 15 with n = 5

Is there a way to achieve this without making it too difficult to read? Could a class based decorator make a difference? I can think of solutions, but none are pretty. Otherwise I will have to rewrite in a different way, but the decorator + argument method is clean.

For readability and reusability you can generalize my ad-hoc decorator above into a reusable decorator with_self that transforms a self-accepting action function into a method-wrapping decorator:

def with_self(func):
    def decorator(method):
        def wrapper(self, *args, **kwargs):
            return func(self)(method)(self, *args, **kwargs)
        return wrapper
    return decorator

@with_self
def add_n(self):
    return add(self.n)

class Foo:
    def __init__(self, n):
        self.n = n

    @add_n
    def multiply(self, multiplier):
        return self.n * multiplier

print(Foo(5).multiply(2)) # outputs 15

Demo here

1 Like

Instead of passing a value, you could pass an accessor that accepts self as an argument:

# alternately we can write `lambda obj: obj.n` ourselves
from operator import attrgetter

def method_add(get_value):
    def decorator(func):
        # It's assumed that this will decorate a method, therefore `func`
        # has a `self` parameter that comes first, which we can therefore
        # reflect in the wrapper
        def wrapper(self, *args, **kwargs):
            return func(self, *args, **kwargs) + get_value(self)
        return wrapper
    return decorator

class Foo:
    def __init__(self, n):
        self.n = n
    @method_add(attrgetter('n'))
    def multiply(self, multiplier):
        return self.n * multiplier

Testing it:

>>> Foo(5).multiply(2)
15

Ben’s new suggestion basically generalizes and refactors this.

1 Like

Thank you Ben, this solved my problem. However, I have to admit that even though I implemented this in my code and it works as desired, I struggle to wrap my head around it with this many layers of decorators. Therefore I’ve used the other solution posted in this thread.

Thank you, excellent solution. I’ve ultimately decided to use this in my code.

I’m glad it was helpful. Just keep in mind that the new decorator might not work so well for decorating non-methods :wink:

You’re welcome. Note that @kknechtel’s solution requires modification to the original decorator (add in the example), whereas my solution is based on the assumption that the decorator is imported from a 3rd-party module you don’t maintain, hence the necessary layer of an additional wrapper.

Good to hear your clarification that you do have control over the decorator code in this case.

1 Like

I realized that I do not need the attrgetter passed as an argument. Using same idea I can simply use getattr(self, "n") in a decorator without an argument.

def add(func):
    def wrapper(self, *args, **kwargs):
        return func(self, *args, **kwargs) + getattr(self, "n") # Get "n" from `self`
    return wrapper

class Foo:
    def __init__(self, n):
        self.n = n

    @add # No argument needed.
    def multiply(self, multiplier):
        return self.n * multiplier

print(Foo(5).multiply(2))

Yes, if you have access to modify the decorator and you don’t need the decorator to be customizable with arguments then this would be the simplest way to do it.

getattr(self, "n") can be more simply written as self.n, by the way.

Thanks for pointing that out. I’m not used to accessing attributes like that in a function outside of a class but indeed, self is just a class instance with its attributes available.

This thread was a great learning experience.