Decorators with parameters?
Solution 1
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.
Solution 2
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
Solution 3
Here is a slightly modified version of t.dubrownik's answer. Why?
- As a general template, you should return the return value from the original function.
- This changes the name of the function, which could affect other decorators / code.
So use @functools.wraps()
:
from functools import wraps
def create_decorator(argument):
def decorator(function):
@wraps(function)
def wrapper(*args, **kwargs):
funny_stuff()
something_with_argument(argument)
retval = function(*args, **kwargs)
more_funny_stuff()
return retval
return wrapper
return decorator
Solution 4
I'd like to show an idea which is IMHO quite elegant. The solution proposed by t.dubrownik shows a pattern which is always the same: you need the three-layered wrapper regardless of what the decorator does.
So I thought this is a job for a meta-decorator, that is, a decorator for decorators. As a decorator is a function, it actually works as a regular decorator with arguments:
def parametrized(dec):
def layer(*args, **kwargs):
def repl(f):
return dec(f, *args, **kwargs)
return repl
return layer
This can be applied to a regular decorator in order to add parameters. So for instance, say we have the decorator which doubles the result of a function:
def double(f):
def aux(*xs, **kws):
return 2 * f(*xs, **kws)
return aux
@double
def function(a):
return 10 + a
print function(3) # Prints 26, namely 2 * (10 + 3)
With @parametrized
we can build a generic @multiply
decorator having a parameter
@parametrized
def multiply(f, n):
def aux(*xs, **kws):
return n * f(*xs, **kws)
return aux
@multiply(2)
def function(a):
return 10 + a
print function(3) # Prints 26
@multiply(3)
def function_again(a):
return 10 + a
print function(3) # Keeps printing 26
print function_again(3) # Prints 39, namely 3 * (10 + 3)
Conventionally the first parameter of a parametrized decorator is the function, while the remaining arguments will correspond to the parameter of the parametrized decorator.
An interesting usage example could be a type-safe assertive decorator:
import itertools as it
@parametrized
def types(f, *types):
def rep(*args):
for a, t, n in zip(args, types, it.count()):
if type(a) is not t:
raise TypeError('Value %d has not type %s. %s instead' %
(n, t, type(a))
)
return f(*args)
return rep
@types(str, int) # arg1 is str, arg2 is int
def string_multiply(text, times):
return text * times
print(string_multiply('hello', 3)) # Prints hellohellohello
print(string_multiply(3, 3)) # Fails miserably with TypeError
A final note: here I'm not using functools.wraps
for the wrapper functions, but I would recommend using it all the times.
Solution 5
I presume your problem is passing arguments to your decorator. This is a little tricky and not straightforward.
Here's an example of how to do this:
class MyDec(object):
def __init__(self,flag):
self.flag = flag
def __call__(self, original_func):
decorator_self = self
def wrappee( *args, **kwargs):
print 'in decorator before wrapee with flag ',decorator_self.flag
original_func(*args,**kwargs)
print 'in decorator after wrapee with flag ',decorator_self.flag
return wrappee
@MyDec('foo de fa fa')
def bar(a,b,c):
print 'in bar',a,b,c
bar('x','y','z')
Prints:
in decorator before wrapee with flag foo de fa fa
in bar x y z
in decorator after wrapee with flag foo de fa fa
Comments
-
falek.marcin almost 2 years
I have a problem with the transfer of the variable
insurance_mode
by the decorator. I would do it by the following decorator statement:@execute_complete_reservation(True) def test_booking_gta_object(self): self.test_select_gta_object()
but unfortunately, this statement does not work. Perhaps maybe there is better way to solve this problem.
def execute_complete_reservation(test_case,insurance_mode): def inner_function(self,*args,**kwargs): self.test_create_qsf_query() test_case(self,*args,**kwargs) self.test_select_room_option() if insurance_mode: self.test_accept_insurance_crosseling() else: self.test_decline_insurance_crosseling() self.test_configure_pax_details() self.test_configure_payer_details return inner_function
-
Brian Clapper about 13 yearsYour example is not syntactically valid.
execute_complete_reservation
takes two parameters, but you're passing it one. Decorators are just syntactic sugar for wrapping functions inside other functions. See docs.python.org/reference/compound_stmts.html#function for complete documentation.
-
-
Admin about 13 yearsBeware of decorator classes. They don't work on methods unless you manually reinvent the logic of instancemethod descriptors.
-
Ross Rogers about 13 yearsdelnan, care to elaborate? I've only had to use this pattern once, so I haven't hit any of the pitfalls yet.
-
falek.marcin about 13 yearsThanks, your solution is more suited to my problem - Easily explains how to create decorators with the parameters
-
Alois Mahdal about 11 yearsI just did this with lambdas all over the place. (read: Python is awesome!) :)
-
jamesc over 10 years@RossRogers My guess is that @delnan is referring to things like
__name__
which an instance of the decorator class won't have? -
Admin over 10 years@jamesc That too, though that's relatively easy to solve. The specific case I was referring to was
class Foo: @MyDec(...) def method(self, ...): blah
which does not work becauseFoo().method
won't be a bound method and won't passself
automatically. This too can be fixed, by makingMyDec
a descriptor and creating bound methods in__get__
, but it's more involved and much less obvious. In the end, decorator classes are not as convenient as they seem. -
Michel Müller about 10 yearsI wonder why GVR didn't implement it by passing in the parameters as subsequent decorator arguments after 'function'. 'Yo dawg I heard you like closures...' etcetera.
-
senderle over 9 years@MichelMüller, interesting! But it occurs to me that you'd need to adopt a convention. Would
function
be the first argument or last? I could make cases for either. The choice seems arbitrary, and so you'd have another random thing to memorize about the language. It's also weird that you'd "call" the function with a signature different from the one in the definition. I suppose that also happens with class methods -- but classes are full-blown language constructs with deep semantics, whereas decorators are just supposed to be syntactic sugar. -
Michel Müller over 9 years> Would function be the first argument or last? Obviously first, since the parameters is a parameter list of variable length. > It's also weird that you'd "call" the function with a signature different from the one in the definition. As you point out, it would fit pretty well actually - it's pretty much analogous to how a class method is called. To make it more clear, you could have something like decorator(self_func, param1, ...) convention. But note: I'm not advocating for any change here, Python is too far down the road for that and we can see how breaking changes have worked out..
-
socketpair almost 9 yearsyou forgot VERY USEFUL functools.wraps for decorating wrapper :)
-
Raffi over 8 yearsIt works but I am sometimes having problems with
argument
that is not seen in the innerwrapper
function (2.7.6, osx apple version). Do you know if this issue is a known issue? -
formiaczek over 8 yearsYou forgot about return when calling function, i.e.
return function(*args, **kwargs)
-
HaPsantran over 8 years@delnan I'd like to see this caveat featured more prominently. I'm hitting it and am interested in seeing a solution that DOES work (more involved an less obvious though it may be).
-
mouckatron over 6 yearsDidn't use this exactly, but helped me get my head around the concept :) Thanks!
-
Jeff over 6 yearsI tried this and had some issues.
-
Dacav over 6 years@Jeff could you share with us the kind of issues you had?
-
Jeff over 6 yearsI had it linked on my question, and I did figure it out... I needed to call
@wraps
in mine for my particular case. -
Dacav over 6 yearsAha! I suspected that, as it was the only possible fault that I could spot. Indeed I changed my answer and appended a mention about
wraps
. True story :) -
Patrick Mevzek over 6 yearsMaybe obvious, but just in case: you need to use this decorator as
@decorator()
and not just@decorator
, even if you have only optional arguments. -
zsf222 over 6 yearsYour answer perfectly explained the inherent orthogonality of the decorator, thank you
-
Mohammad Banisaeid over 6 yearsTo make this complete, you may also want to add
@functools.wraps(function)
above thedef wrapper...
line. -
z33k over 6 yearsIt works but I have trouble getting my head around it. How would one translate the decorators' syntactic sugar into mundane calls in that case? Keeping with your example would it be:
function = parametrized(multiply(function, 2))
and then callingfunction(3)
to get26
? It doesn't work when I try it with my analogues -
Dacav over 6 years@o'rety I'm a bit confused by your question. Could you elaborate?
-
z33k over 6 yearsOh boy, I lost a whole day on this. Thankfully, I came around this answer (which incidentally could be the best answer ever created on the whole internet). They too use your
@parametrized
trick. The problem I had was I forgot the@
syntax equals actual calls (somehow I knew that and didn't know that at the same time as you can gather from my question). So if you want to translate@
syntax into mundane calls to check how it works, you better comment it out temporarily first or you'd end up calling it twice and getting mumbojumbo results -
Mr_and_Mrs_D almost 6 yearsCould you add
@functools.wraps
? -
srj almost 6 years@Mr_and_Mrs_D , I've updated the post with an example with
functool.wraps
. Adding it in the example may confuse readers further. -
Stefan Falk almost 6 yearsWhat is
arg
here!? -
srj almost 6 years@StefanFalk
arg
is just a variable name, with the value that you'd use for creating thereal_decorator
out of_pseudo_decor
-
Chang Zhao over 5 yearsHow will you pass argument passed to
bar
to the argument ofreal_decorator
? -
srj over 5 years@ChangZhao as described above, even though you use the name
bar
in the code after the declaration, python "points" the namebar
to the result ofreal_decorator(bar)
so any arguments that you pass while callingbar(arg1, arg2..)
is in fact interpreted asreal_decorator(bar)(arg1, arg2,..)
-
Chang Zhao over 5 yearshow will that work with partial ? because you are passing arg !
-
srj over 5 yearsTry it out for yourself. hopefully this piece of code is self-explanatory. repl.it/@sreedom/SpotlessTastyScreencast
-
Gyula Sámuel Karli over 5 yearsIt's cool on one hand but I don't understand the reason for having 3 methods encapsulating to each other. Namely I don't understand what on the earth repl() does, is it just a trick with scopes and param numbers?
-
Dacav over 5 years
repl
stands for replacement. When I name it repl it means that it will effectively take the place of another function. Assuming that you are talking about theparametrized
decorator,layer
will replace the decorated decorator. Thedec
parameter ofparametrized
is the decorator we are going to replace withlayer
. Decorators take functions as parameter, and indeed we passf
todec
. So, ultimately,repl
is what replacesdec
, and works by applying the decoratordec
so that it replacesf
. I hope that this comment makes it clearer, but I myself have to read it many times. -
Arindam Roychowdhury over 5 yearsThis works. Although yes, this makes it hard to set the value to the decorator.
-
Nicholas Humphrey about 5 yearsThanks, years late but this solved my problem. Finally I'm feeling that I'm grasping the idea of decorators.
-
Cho almost 5 yearsThe nicest explanation I've ever seen!
-
norok2 almost 5 years
-
Stephen Ellwood almost 5 yearsThis seems like a great answer but there are some steps here I do not understand. Why copy self into decorator_self. Is this to get around the issue mentioned above?
-
Ross Rogers almost 5 yearsI don't think the
decorator_self
is necessary anymore. It was because I hadself
as the first parameter ofwrapee
way back when I was experimenting. Try it with out it, just usingself
and not aliasingself
todecorator_self
-
Radha Satam over 4 yearsThank you for that explanation. The link to the pycon talk was very helpful too.
-
Shital Shah over 4 yearsWhy do you need to check in
locals()
? -
norok2 over 4 years@ShitalShah that covers the case where the decorator is used without
()
. -
Ahmed Gad about 4 yearsPerfect explanation. This is what every learner to Python decorators is looking for.
-
Tian about 4 yearsAlthough this is the solution, it really does'nt seem elegant or pythonic. I always viewed decorators as like object methods, which pass in
self
as an argument automatically when called. Having an additional function layer to handle arguments just does not feel right -
medley56 almost 4 yearsThis is freaking genius.
-
hasdrubal almost 4 yearsSo how will it work when you don't know
arg
until it is time to actually run the function? aka you want to surround a string with an HTML tag but the used tag can be different every time (or even user defined)? -
Lee Loftiss almost 4 yearsThe video was great. Thanks!
-
Milo Persic over 3 yearsOf all the posts here, this answer proved the most useful for my understanding of how the argument is passed and handled.
-
user8491363 over 3 yearsIn
numba
library, there's@jit(nopython=True)
expression. How's that possible then? In this scenario, I don't see hownumba
is casually just getting an argument. -
srj over 3 yearsI've edited the example with
wraps
to be clearer in the use ofarg
. -
Philip Couling over 3 years@Titan I'd say it's very pythoninc. The nested methods is to allow you to define decorators separate to using them. It makes it possible to have two steps
x=decorator_factory(foo)
@x; def ...
as well as the more common single step@decorator_factory(foo); def ...
. Though it's less common, it is used and very useful. -
Leon Chang about 3 yearsThis helps me understand how Flask's @app.route(path) maps to or calls its view function. Thanks.
-
Lucas Andrade about 3 yearsI did exactly that, but on AWS lambdas with flask it doesn't work: python 3.8 returns this error:
AssertionError: View function mapping is overwriting an existing endpoint function: authorization_required_wrapper
-
jouell almost 3 yearswhy wouldn't "foo = decorator_with_args(arg)(foo)" be "foo = decorator_with_args(foo(arg))" instead?
-
srj almost 3 years@jouell, because python will execute the
@decorator_with_args(arg)
first , the result of that will take in the function object created by thedef
statement. -
jouell almost 3 yearsRight. Thank you. @srj
-
Amer Sawan over 2 yearsNote that this will not work similiar to the normal decorator, if the
any_number_of_arguments
is optional arg, you still have to write()
in the end of the decorator. -
user41855 over 2 years@srj It actually is
foo = decorator_with_args(foo)(arg)
.decorator_with_args(foo)
will return the wrapper function. This wrapper function needs 'args' as arguments. Hencewrapper(arg)
will be executed next -
nish over 2 yearsPlease expound on " Python expects completely different behavior in these two cases"
-
Tommy about 2 yearsthis is my favorite answer here as wraps is critical.
-
Jennings about 2 yearsThanks for this answer, took me some time to...*wrap* my head around this concept...ba dum tsk... lol. So the key concept here for me was that this is 3 layers deep. Found some more related info: realpython.com/primer-on-python-decorators/…
-
chilicheech about 2 yearsHow about changing the multiplying function's return from
return _decorator(f_py) if callable(f_py) else _decorator
toreturn _decorator(f_py) if f_py else _decorator
orreturn _decorator if f_py is None else _decorator(f_py)
since you've already asserted that it's either a callable or None. This should be "more efficient" than callingcallable
a second time.