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.
Usage:
@decorator_factory("Some argument")
def function_to_be_decorated(args):
print(f"Do something with '{args}'.")
decorator_factory("Some argument") uses the given argument to create a standard, argumentless decorator. So the following block is functionally identical to the one above:
created_decorator = decorator_factory("Some argument")
@created_decorator
def function_to_be_decorated(args):
print(f"Do something with '{args}'.")
Answer from t.dubrownik on Stack OverflowThe 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.
Usage:
@decorator_factory("Some argument")
def function_to_be_decorated(args):
print(f"Do something with '{args}'.")
decorator_factory("Some argument") uses the given argument to create a standard, argumentless decorator. So the following block is functionally identical to the one above:
created_decorator = decorator_factory("Some argument")
@created_decorator
def function_to_be_decorated(args):
print(f"Do something with '{args}'.")
Edit : for an in-depth understanding of the mental model of decorators, take a look at this awesome Pycon Talk. well worth the 30 minutes.
One way of thinking about decorators with arguments is
@decorator
def foo(*args, **kwargs):
pass
translates to
foo = decorator(foo)
So if the decorator had arguments,
@decorator_with_args(arg)
def foo(*args, **kwargs):
pass
translates to
foo = decorator_with_args(arg)(foo)
decorator_with_args is a function which accepts a custom argument and which returns the actual decorator (that will be applied to the decorated function).
I use a simple trick with partials to make my decorators easy
from functools import partial
def _pseudo_decor(fun, argument):
def ret_fun(*args, **kwargs):
#do stuff here, for eg.
print ("decorator arg is %s" % str(argument))
return fun(*args, **kwargs)
return ret_fun
real_decorator = partial(_pseudo_decor, argument=arg)
@real_decorator
def foo(*args, **kwargs):
pass
Update:
Above, foo becomes real_decorator(foo)
One effect of decorating a function is that the name foo is overridden upon decorator declaration. foo is "overridden" by whatever is returned by real_decorator. In this case, a new function object.
All of foo's metadata is overridden, notably docstring and function name.
>>> print(foo)
<function _pseudo_decor.<locals>.ret_fun at 0x10666a2f0>
functools.wraps gives us a convenient method to "lift" the docstring and name to the returned function.
from functools import partial, wraps
def _pseudo_decor(fun, argument):
# magic sauce to lift the name and doc of the function
@wraps(fun)
def ret_fun(*args, **kwargs):
# pre function execution stuff here, for eg.
print("decorator argument is %s" % str(argument))
returned_value = fun(*args, **kwargs)
# post execution stuff here, for eg.
print("returned value is %s" % returned_value)
return returned_value
return ret_fun
real_decorator1 = partial(_pseudo_decor, argument="some_arg")
real_decorator2 = partial(_pseudo_decor, argument="some_other_arg")
@real_decorator1
def bar(*args, **kwargs):
pass
>>> print(bar)
<function __main__.bar(*args, **kwargs)>
>>> bar(1,2,3, k="v", x="z")
decorator argument is some_arg
returned value is None
How does a decorator with arguments work behind the screen?
Passing arguments to a decorator in a class
How to use self with decorator?
Why does Python @property decorator take 4 arguments if we only pass it 1?
I am learning python decorators and having some doubts with decorators with arguments. I will try to make my point clear (Or my doubts, better say) with examples:
If I execute the following piece of code:
def do_function_twice(func):
print("Inside decorator")
@functools.wraps(func)
def wrapper(*arg):
print("Inside wrapper")
func(*arg)
return func(*arg)
return wrapper
@do_function_twice
def print_msg(msg):
print(msg)
return f"What is up buddy"
I receive Inside decorator as I was expecting, because behind the scenes python did print_msg = do_function_twice(print_msg)
What if I try creating a decorator with an argument like the following?
def do_sth_if_true(bool_):
print("Inside do_sth_if_true")
def repeat_func(func):
print("Inside repeat_func")
func()
return func()
return repeat_func
@do_sth_if_true(True)
def yeah():
print("Yeah")If I execute the script I receive:
Inside do_sth_if_true Inside repeat_func Yeah Yeah
I can imagine it is doing yeah = do_sth_if_true(True)(yeah) behind the scenes (So it is calling the wrapping function, in this example called repeat_func. My question would be: How does python know which way to apply the decorator?