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.
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:
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.
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:
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.
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.
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.
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.