How to Pass Extra Arguments to a Python Decorator

How do I pass extra arguments to a Python decorator?

Since you are calling the decorator like a function, it needs to return another function which is the actual decorator:

def my_decorator(param):
def actual_decorator(func):
print("Decorating function {}, with parameter {}".format(func.__name__, param))
return function_wrapper(func) # assume we defined a wrapper somewhere
return actual_decorator

The outer function will be given any arguments you pass explicitly, and should return the inner function. The inner function will be passed the function to decorate, and return the modified function.

Usually you want the decorator to change the function behavior by wrapping it in a wrapper function. Here's an example that optionally adds logging when the function is called:

def log_decorator(log_enabled):
def actual_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if log_enabled:
print("Calling Function: " + func.__name__)
return func(*args, **kwargs)
return wrapper
return actual_decorator

The functools.wraps call copies things like the name and docstring to the wrapper function, to make it more similar to the original function.

Example usage:

>>> @log_decorator(True)
... def f(x):
... return x+1
...
>>> f(4)
Calling Function: f
5

Decorators with parameters?

The syntax for decorators with arguments is a bit different - the decorator with arguments should return a function that will take a function and return another function. So it should really return a normal decorator. A bit confusing, right? What I mean is:

def decorator_factory(argument):
def decorator(function):
def wrapper(*args, **kwargs):
funny_stuff()
something_with_argument(argument)
result = function(*args, **kwargs)
more_funny_stuff()
return result
return wrapper
return decorator

Here you can read more on the subject - it's also possible to implement this using callable objects and that is also explained there.

How to pass variables with values in decorators?

There are lots of SO questions and answers for your base question of how to add parameters to decorators: Decorators with parameters? , How do I pass extra arguments to a Python decorator?

In your case, you could wrap your existing decorator in another layer that accepts keyword arguments as well and those keyword args would be available in the scope of your inner wrapper. Here's a simpler case along the lines of your full case that illustrates what I mean:

def dump_factory(**dump_kwargs):
def dump_args(func):
def wrapper(**kwargs):
variables = kwargs
# check if any of the kwargs from the outer decorator
# have been set, and replace them here if so
for ky, val in dump_kwargs.items():
if ky in variables:
variables[ky] = val
return func(**variables)
return wrapper
return dump_args

@dump_factory(b=1)
def test(a =0, b=4, e=0, c="blah-blah", d= 4.5, val=True):
print(f" b = {b}")

test(b='a')
# b = 1

Pass multiple arguments to a decorator in Python

First, I'd advise that you have a look at some tutorial on decorators, they are pretty cool and you definitely need to understand the basics if you want to use flask. I personally quite like this RealPython tutorial.

Second, you have two solutions : either default second argument or argument packing.

def permission_required(permission1, permission2=None):
...

or

def permission_required(*perms):
...

I personaly way prefer the second option.
Example:

def permission_required(*perms):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
for perm in perms:
if not current_user.can(perm):
abort(403)
return f(*args, **kwargs)
return decorated_function
return decorator

How can I pass a variable in a decorator to function's argument in a decorated function?

You can't pass it as its own name, but you can add it to the keywords.

def decorate(function):
def wrap_function(*args, **kwargs):
kwargs['str'] = 'Hello!'
return function(*args, **kwargs)
return wrap_function

@decorate
def print_message(*args, **kwargs):
print(kwargs['str'])

Alternatively you can name its own argument:

def decorate(function):
def wrap_function(*args, **kwargs):
str = 'Hello!'
return function(str, *args, **kwargs)
return wrap_function

@decorate
def print_message(str, *args, **kwargs):
print(str)

Class method:

def decorate(function):
def wrap_function(*args, **kwargs):
str = 'Hello!'
args.insert(1, str)
return function(*args, **kwargs)
return wrap_function

class Printer:
@decorate
def print_message(self, str, *args, **kwargs):
print(str)

How can I pass arguments to decorator, process there, and forward to decorated function?

The reason is immediate after considering how the decorator transforms the function and that functions are objects themselves in Python.

Let's start from the latter.

Functions are objects:

This is immediate when we consider the meaning of two pairs of parenthesis after a function name. Consider this simple example (Python 3):

def func(x):
def func2(y):
return x + y + 1
return func2

result = func(5)(10)
print(result) # 15

Here "func" returns a function object "func2" and therefore you can use:

func(5)(10)

You can view this as calling first

func(5)

and applying "(10)" to the resulting object that is a function! So you have:

func2(10)

Now since both "x" and "y" are defined, "func2" can return the final value to "result".

Remember, this is all possible because functions are object themselves and because "func" returns a function object

func2

and not its result (it is not invoking the function on its own)

func2()

In short, that means that with wrapped functions the second set of arguments is for the inner function (if the wrapper returns the inner function object).

Decorators:

In your example, "main" calls "fun1" in the last line with

return fun1(decarg)

Due to the decorator

@dec(decarg)

In reality you can think of "fun1" as:

fun1 = dec(decarg)(fun1)

Therefore, the last line in "main" is equivalent to:

return dec(decarg)(fun1)(decarg)

With the previous explanation it should be trivial to find the problem!

  • dec(decarg) gets executed and returns a "_dec" function object; note that this "decarg" is the one passed in the first parenthesis and thus in the decorator.
  • _dec(fun1) gets executed and returns a "_fun" function object.
  • _fun(decarg) gets executed and invokes fun1(decargs) with the return statement and this will correctly translate in fun1(3) that is the result you get; note that this "decarg" is the one passed in the third parenthesis and thus when you invoke "fun1" in main.

You don't get 13 as a result because you don't invoke "fun1" with the result from

funarg = decarg + 7

as argument, but rather you invoke it with "decarg" that is passed to "_fun" as positional argument (funarg=decarg) from main.

Anyway, I have to thank you for this question, because I was looking for a neat way to pass an argument to a decorator only when invoking a function, and this works very nicely.

Here is another example that might help:

from functools import wraps

def add(addend):
def decorator(func):
@wraps(func)
def wrapper(p1, p2=101):
for v in func(p1, p2):
yield v + addend
return wrapper
return decorator

def mul(multiplier):
def decorator(func):
@wraps(func)
def wrapper(p1, p2=101):
for v in func(p1, p2):
yield v * multiplier
return wrapper
return decorator

def super_gen(p1, p2=101, a=0, m=1):
@add(a)
@mul(m)
def gen(p1, p2=101):
for x in range(p1, p2):
yield x
return gen(p1, p2)


Related Topics



Leave a reply



Submit