690

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
Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
falek.marcin
  • 9,679
  • 9
  • 28
  • 33
  • 5
    Your 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 http://docs.python.org/reference/compound_stmts.html#function for complete documentation. – Brian Clapper May 08 '11 at 17:50

23 Answers23

1159

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.

Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
t.dubrownik
  • 12,464
  • 1
  • 17
  • 6
  • 2
    I just did this with lambdas all over the place. (read: Python is awesome!) :) – Alois Mahdal Jun 06 '13 at 15:36
  • 131
    I 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. – Michel Müller Apr 08 '14 at 16:22
  • 1
    @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. – senderle Feb 18 '15 at 12:18
  • 7
    > 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.. – Michel Müller Feb 19 '15 at 01:07
  • 43
    you forgot VERY USEFUL functools.wraps for decorating wrapper :) – socketpair Aug 13 '15 at 21:19
  • It works but I am sometimes having problems with `argument` that is not seen in the inner `wrapper` function (2.7.6, osx apple version). Do you know if this issue is a known issue? – Raffi Nov 12 '15 at 13:56
  • 12
    You forgot about return when calling function, i.e. `return function(*args, **kwargs)` – formiaczek Dec 01 '15 at 17:09
  • 76
    Maybe obvious, but just in case: you need to use this decorator as `@decorator()` and not just `@decorator`, even if you have only optional arguments. – Patrick Mevzek Dec 04 '17 at 20:25
  • 4
    To make this complete, you may also want to add `@functools.wraps(function)` above the `def wrapper...` line. – Mohammad Banisaeid Dec 24 '17 at 11:14
  • Although 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 – Tian Jun 02 '20 at 04:41
  • 2
    @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. – Philip Couling Jan 30 '21 at 11:59
  • I'm wondering what the canonical nomenclature is for this case of decorator with arguments. I've heard, for the nested functions from outer to inner, of: ``, `decorator_`, `wrapper` (for ``, the name of your decorator). Any contradicting or stronger conventions? – LoneCodeRanger Feb 13 '23 at 11:22
513

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
srj
  • 9,591
  • 2
  • 23
  • 27
  • 9
    Your answer perfectly explained the inherent orthogonality of the decorator, thank you – zsf222 Dec 09 '17 at 15:58
  • Could you add `@functools.wraps`? – Mr_and_Mrs_D Aug 26 '18 at 20:48
  • 1
    @Mr_and_Mrs_D , I've updated the post with an example with `functool.wraps`. Adding it in the example may confuse readers further. – srj Aug 28 '18 at 02:31
  • 12
    What is `arg` here!? – Stefan Falk Sep 25 '18 at 10:22
  • @StefanFalk `arg` is just a variable name, with the value that you'd use for creating the `real_decorator` out of `_pseudo_decor` – srj Sep 26 '18 at 03:52
  • 1
    How will you pass argument passed to `bar` to the argument of `real_decorator` ? – Chang Zhao Nov 01 '18 at 02:56
  • @ChangZhao as described above, even though you use the name `bar` in the code after the declaration, python "points" the name `bar` to the result of `real_decorator(bar)` so any arguments that you pass while calling `bar(arg1, arg2..)` is in fact interpreted as `real_decorator(bar)(arg1, arg2,..)` – srj Nov 01 '18 at 06:20
  • how will that work with partial ? because you are passing arg ! – Chang Zhao Nov 01 '18 at 08:23
  • Try it out for yourself. hopefully this piece of code is self-explanatory. https://repl.it/@sreedom/SpotlessTastyScreencast – srj Nov 04 '18 at 06:48
  • 1
    So 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)? – hasdrubal Aug 25 '20 at 12:40
  • In `numba` library, there's `@jit(nopython=True)` expression. How's that possible then? In this scenario, I don't see how `numba` is casually just getting an argument. – user8491363 Dec 21 '20 at 15:28
  • I've edited the example with `wraps` to be clearer in the use of `arg`. – srj Jan 21 '21 at 08:18
  • 1
    why wouldn't "foo = decorator_with_args(arg)(foo)" be "foo = decorator_with_args(foo(arg))" instead? – jouell Sep 07 '21 at 02:58
  • @jouell, because python will execute the `@decorator_with_args(arg)` first , the result of that will take in the function object created by the `def` statement. – srj Sep 16 '21 at 05:57
  • @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. Hence `wrapper(arg)` will be executed next – user41855 Feb 06 '22 at 09:58
  • For a solution with more flexibility: `def decor_with_argument(argument: str): return partial(_pseudo_decor, argument=argument)` In action: `@decor_with_argument(argument="some_arg")` – johnklawlor Jun 23 '23 at 17:42
155

Here is a slightly modified version of t.dubrownik's answer. Why?

  1. As a general template, you should return the return value from the original function.
  2. 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
Mateen Ulhaq
  • 24,552
  • 19
  • 101
  • 135
Ross R
  • 8,853
  • 7
  • 28
  • 27
  • 5
    I 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` – Lucas Andrade May 10 '21 at 18:55
  • 2
    this is my favorite answer here as wraps is critical. – Tommy May 23 '22 at 15:35
  • 1
    Thanks 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: https://realpython.com/primer-on-python-decorators/#decorators-with-arguments – Jennings Jun 06 '22 at 19:10
123

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Dacav
  • 13,590
  • 11
  • 60
  • 87
  • 5
    Didn't use this exactly, but helped me get my head around the concept :) Thanks! – mouckatron Oct 10 '17 at 22:04
  • I tried this and had some [issues](https://stackoverflow.com/questions/46734219/flask-error-with-two-parameterized-functions). – Jeff Oct 14 '17 at 14:53
  • @Jeff could you share with us the kind of issues you had? – Dacav Oct 14 '17 at 16:49
  • I had it linked on my question, and I did figure it out... I needed to call `@wraps` in mine for my particular case. – Jeff Oct 14 '17 at 19:33
  • Aha! 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 :) – Dacav Oct 15 '17 at 21:00
  • It 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 calling `function(3)` to get `26`? It doesn't work when I try it with my analogues – z33k Mar 10 '18 at 18:55
  • @o'rety I'm a bit confused by your question. Could you elaborate? – Dacav Mar 13 '18 at 13:31
  • 4
    Oh boy, I lost a whole day on this. Thankfully, I came around [this answer](https://stackoverflow.com/a/1594484/4465708) (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 – z33k Mar 13 '18 at 14:50
  • It'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? – Gyula Sámuel Karli Nov 24 '18 at 17:00
  • `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 the `parametrized` decorator, `layer` will replace the decorated decorator. The `dec` parameter of `parametrized` is the decorator we are going to replace with `layer`. Decorators take functions as parameter, and indeed we pass `f` to `dec`. So, ultimately, `repl` is what replaces `dec`, and works by applying the decorator `dec` so that it replaces `f`. I hope that this comment makes it clearer, but I myself have to read it many times. – Dacav Nov 24 '18 at 17:05
  • This is the most handy approach in Python that I have seen. Very cool and easy to use! All thumbs up! :-) – Matt Nov 15 '22 at 09:40
44

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

See Bruce Eckel's article for more details.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ross Rogers
  • 23,523
  • 27
  • 108
  • 164
  • 23
    Beware of decorator classes. They don't work on methods unless you manually reinvent the logic of instancemethod descriptors. –  May 08 '11 at 18:01
  • 9
    delnan, care to elaborate? I've only had to use this pattern once, so I haven't hit any of the pitfalls yet. – Ross Rogers May 08 '11 at 18:04
  • 2
    @RossRogers My guess is that @delnan is referring to things like `__name__` which an instance of the decorator class won't have? – jamesc Jan 13 '14 at 17:18
  • 9
    @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 because `Foo().method` won't be a bound method and won't pass `self` automatically. This too can be fixed, by making `MyDec` 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. –  Jan 13 '14 at 21:49
  • 2
    @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). – HaPsantran Mar 13 '16 at 06:42
  • This 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? – Stephen Ellwood Oct 01 '19 at 11:57
  • I don't think the `decorator_self` is necessary anymore. It _was_ because I had `self` as the first parameter of `wrapee` way back when I was experimenting. Try it with out it, just using `self` and not aliasing `self` to `decorator_self` – Ross Rogers Oct 01 '19 at 16:00
44

Writing a decorator that works with and without parameter is a challenge because Python expects completely different behavior in these two cases! Many answers have tried to work around this and below is an improvement of answer by @norok2. Specifically, this variation eliminates the use of locals().

Following the same example as given by @norok2:

import functools

def multiplying(f_py=None, factor=1):
    assert callable(f_py) or f_py is None
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            return factor * func(*args, **kwargs)
        return wrapper
    return _decorator(f_py) if callable(f_py) else _decorator


@multiplying
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying()
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying(factor=10)
def summing(x): return sum(x)

print(summing(range(10)))
# 450

Play with this code.

The catch is that the user must supply key,value pairs of parameters instead of positional parameters and the first parameter is reserved.

norok2
  • 25,683
  • 4
  • 73
  • 99
Shital Shah
  • 63,284
  • 17
  • 238
  • 185
  • 3
    This is freaking genius. – medley56 Aug 14 '20 at 18:05
  • Please expound on " Python expects completely different behavior in these two cases" – nish Mar 08 '22 at 12:01
  • How about changing the multiplying function's return from `return _decorator(f_py) if callable(f_py) else _decorator` to `return _decorator(f_py) if f_py else _decorator` or `return _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 calling `callable` a second time. – chilicheech Jun 13 '22 at 23:26
  • This is the pattern I was looking for - a decorator that can be used both with and without args. The examples I have experience with are Click (the command-builder from Flask project) and Dramatiq (the task queue alternative to Celery). I particularly recommend checking out Dramatiq's implementation: https://github.com/Bogdanp/dramatiq/blob/master/dramatiq/actor.py – odigity Feb 19 '23 at 17:59
24
def decorator(argument):
    def real_decorator(function):
        def wrapper(*args):
            for arg in args:
                assert type(arg)==int,f'{arg} is not an interger'
            result = function(*args)
            result = result*argument
            return result
        return wrapper
    return real_decorator

Usage of the decorator

@decorator(2)
def adder(*args):
    sum=0
    for i in args:
        sum+=i
    return sum

Then the

adder(2,3)

produces

10

but

adder('hi',3)

produces

---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-143-242a8feb1cc4> in <module>
----> 1 adder('hi',3)

<ipython-input-140-d3420c248ebd> in wrapper(*args)
      3         def wrapper(*args):
      4             for arg in args:
----> 5                 assert type(arg)==int,f'{arg} is not an interger'
      6             result = function(*args)
      7             result = result*argument

AssertionError: hi is not an interger
Gajendra D Ambi
  • 3,832
  • 26
  • 30
  • 4
    Of all the posts here, this answer proved the most useful for my understanding of how the argument is passed and handled. – Milo Persic Nov 22 '20 at 21:12
21

This is a template for a function decorator that does not require () if no parameters are to be given and supports both positional and keyword parameters (but requires cheching on locals() to find out if the first parameter is the function to be decorated or not):

import functools


def decorator(x_or_func=None, *decorator_args, **decorator_kws):
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kws):
            if 'x_or_func' not in locals() \
                    or callable(x_or_func) \
                    or x_or_func is None:
                x = ...  # <-- default `x` value
            else:
                x = x_or_func
            return func(*args, **kws)

        return wrapper

    return _decorator(x_or_func) if callable(x_or_func) else _decorator

an example of this is given below:

def multiplying(factor_or_func=None):
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            if 'factor_or_func' not in locals() \
                    or callable(factor_or_func) \
                    or factor_or_func is None:
                factor = 1
            else:
                factor = factor_or_func
            return factor * func(*args, **kwargs)
        return wrapper
    return _decorator(factor_or_func) if callable(factor_or_func) else _decorator


@multiplying
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying()
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying(10)
def summing(x): return sum(x)

print(summing(range(10)))
# 450

Alternatively, if one does not need positional arguments, one can relax the need for checking on the first parameter within the wrapper() (thus removing the need to use locals()):

import functools


def decorator(func_=None, **decorator_kws):
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kws):
            return func(*args, **kws)
        return wrapper

    if callable(func_):
        return _decorator(func_)
    elif func_ is None:
        return _decorator
    else:
        raise RuntimeWarning("Positional arguments are not supported.")

an example of this is given below:

import functools


def multiplying(func_=None, factor=1):
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            return factor * func(*args, **kwargs)
        return wrapper

    if callable(func_):
        return _decorator(func_)
    elif func_ is None:
        return _decorator
    else:
        raise RuntimeWarning("Positional arguments are not supported.")


@multiplying
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying()
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying(factor=10)
def summing(x): return sum(x)

print(summing(range(10)))
# 450


@multiplying(10)
def summing(x): return sum(x)
print(summing(range(10)))
# RuntimeWarning Traceback (most recent call last)
#    ....
# RuntimeWarning: Positional arguments are not supported.

(partially reworked from @ShitalShah's answer)

norok2
  • 25,683
  • 4
  • 73
  • 99
18

Simple as this

def real_decorator(any_number_of_arguments):
   def pseudo_decorator(function_to_be_decorated):

       def real_wrapper(function_arguments):
           print(function_arguments)
           result = function_to_be_decorated(any_number_of_arguments)
           return result

       return real_wrapper
   return pseudo_decorator

Now

@real_decorator(any_number_of_arguments)
def some_function(function_arguments):
        return "Any"
Henshal B
  • 1,540
  • 12
  • 13
  • 3
    Note 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. – Amer Sawan Nov 21 '21 at 18:42
  • The question pertains specifically to decorators with parameters and does not concern optional parameters. – Henshal B Aug 02 '23 at 06:10
10

enter image description here

  • Here we ran display info twice with two different names and two different ages.
  • Now every time we ran display info, our decorators also added the functionality of printing out a line before and a line after that wrapped function.
def decorator_function(original_function):
    def wrapper_function(*args, **kwargs):
        print('Executed Before', original_function.__name__)
        result = original_function(*args, **kwargs)
        print('Executed After', original_function.__name__, '\n')
        return result
    return wrapper_function


@decorator_function
def display_info(name, age):
    print('display_info ran with arguments ({}, {})'.format(name, age))


display_info('Mr Bean', 66)
display_info('MC Jordan', 57)

output:

Executed Before display_info
display_info ran with arguments (Mr Bean, 66)
Executed After display_info 

Executed Before display_info
display_info ran with arguments (MC Jordan, 57)
Executed After display_info 
  • So now let's go ahead and get our decorator function to accept arguments.

  • For example let's say that I wanted a customizable prefix to all of these print statements within the wrapper.

  • Now this would be a good candidate for an argument to the decorator.

  • The argument that we pass in will be that prefix. Now in order to do, this we're just going to add another outer layer to our decorator, so I'm going to call this a function a prefix decorator.

def prefix_decorator(prefix):
    def decorator_function(original_function):
        def wrapper_function(*args, **kwargs):
            print(prefix, 'Executed Before', original_function.__name__)
            result = original_function(*args, **kwargs)
            print(prefix, 'Executed After', original_function.__name__, '\n')
            return result
        return wrapper_function
    return decorator_function


@prefix_decorator('LOG:')
def display_info(name, age):
    print('display_info ran with arguments ({}, {})'.format(name, age))


display_info('Mr Bean', 66)
display_info('MC Jordan', 57)

output:

LOG: Executed Before display_info
display_info ran with arguments (Mr Bean, 66)
LOG: Executed After display_info 

LOG: Executed Before display_info
display_info ran with arguments (MC Jordan, 57)
LOG: Executed After display_info 
  • Now we have that LOG: prefix before our print statements in our wrapper function and you can change this any time that you want.
Milovan Tomašević
  • 6,823
  • 1
  • 50
  • 42
7

Great answers above. This one also illustrates @wraps, which takes the doc string and function name from the original function and applies it to the new wrapped version:

from functools import wraps

def decorator_func_with_args(arg1, arg2):
    def decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            print("Before orginal function with decorator args:", arg1, arg2)
            result = f(*args, **kwargs)
            print("Ran after the orginal function")
            return result
        return wrapper
    return decorator

@decorator_func_with_args("foo", "bar")
def hello(name):
    """A function which prints a greeting to the name provided.
    """
    print('hello ', name)
    return 42

print("Starting script..")
x = hello('Bob')
print("The value of x is:", x)
print("The wrapped functions docstring is:", hello.__doc__)
print("The wrapped functions name is:", hello.__name__)

Prints:

Starting script..
Before orginal function with decorator args: foo bar
hello  Bob
Ran after the orginal function
The value of x is: 42
The wrapped functions docstring is: A function which prints a greeting to the name provided.
The wrapped functions name is: hello
run_the_race
  • 1,344
  • 2
  • 36
  • 62
4

In my instance, I decided to solve this via a one-line lambda to create a new decorator function:

def finished_message(function, message="Finished!"):

    def wrapper(*args, **kwargs):
        output = function(*args,**kwargs)
        print(message)
        return output

    return wrapper

@finished_message
def func():
    pass

my_finished_message = lambda f: finished_message(f, "All Done!")

@my_finished_message
def my_func():
    pass

if __name__ == '__main__':
    func()
    my_func()

When executed, this prints:

Finished!
All Done!

Perhaps not as extensible as other solutions, but worked for me.

ZacBook
  • 126
  • 3
4

It is well known that the following two pieces of code are nearly equivalent:

@dec
def foo():
    pass    foo = dec(foo)

############################################
foo = dec(foo)

A common mistake is to think that @ simply hides the leftmost argument.

@dec(1, 2, 3)
def foo():
    pass    
###########################################
foo = dec(foo, 1, 2, 3)

It would be much easier to write decorators if the above is how @ worked. Unfortunately, that’s not the way things are done.


Consider a decorator Waitwhich haults program execution for a few seconds. If you don't pass in a Wait-time then the default value is 1 seconds. Use-cases are shown below.

##################################################
@Wait
def print_something(something):
    print(something)

##################################################
@Wait(3)
def print_something_else(something_else):
    print(something_else)

##################################################
@Wait(delay=3)
def print_something_else(something_else):
    print(something_else)

When Wait has an argument, such as @Wait(3), then the call Wait(3) is executed before anything else happens.

That is, the following two pieces of code are equivalent

@Wait(3)
def print_something_else(something_else):
    print(something_else)

###############################################
return_value = Wait(3)
@return_value
def print_something_else(something_else):
    print(something_else)

This is a problem.

if `Wait` has no arguments:
    `Wait` is the decorator.
else: # `Wait` receives arguments
    `Wait` is not the decorator itself.
    Instead, `Wait` ***returns*** the decorator

One solution is shown below:

Let us begin by creating the following class, DelayedDecorator:

class DelayedDecorator:
    def __init__(i, cls, *args, **kwargs):
        print("Delayed Decorator __init__", cls, args, kwargs)
        i._cls = cls
        i._args = args
        i._kwargs = kwargs
    def __call__(i, func):
        print("Delayed Decorator __call__", func)
        if not (callable(func)):
            import io
            with io.StringIO() as ss:
                print(
                    "If only one input, input must be callable",
                    "Instead, received:",
                    repr(func),
                    sep="\n",
                    file=ss
                )
                msg = ss.getvalue()
            raise TypeError(msg)
        return i._cls(func, *i._args, **i._kwargs)

Now we can write things like:

 dec = DelayedDecorator(Wait, delay=4)
 @dec
 def delayed_print(something):
    print(something)

Note that:

  • dec does not not accept multiple arguments.
  • dec only accepts the function to be wrapped.

    import inspect class PolyArgDecoratorMeta(type): def call(Wait, *args, **kwargs): try: arg_count = len(args) if (arg_count == 1): if callable(args[0]): SuperClass = inspect.getmro(PolyArgDecoratorMeta)[1] r = SuperClass.call(Wait, args[0]) else: r = DelayedDecorator(Wait, *args, **kwargs) else: r = DelayedDecorator(Wait, *args, **kwargs) finally: pass return r

    import time class Wait(metaclass=PolyArgDecoratorMeta): def init(i, func, delay = 2): i._func = func i._delay = delay

    def __call__(i, *args, **kwargs):
        time.sleep(i._delay)
        r = i._func(*args, **kwargs)
        return r 
    

The following two pieces of code are equivalent:

@Wait
def print_something(something):
     print (something)

##################################################

def print_something(something):
    print(something)
print_something = Wait(print_something)

We can print "something" to the console very slowly, as follows:

print_something("something")

#################################################
@Wait(delay=1)
def print_something_else(something_else):
    print(something_else)

##################################################
def print_something_else(something_else):
    print(something_else)

dd = DelayedDecorator(Wait, delay=1)
print_something_else = dd(print_something_else)

##################################################

print_something_else("something")

Final Notes

It may look like a lot of code, but you don't have to write the classes DelayedDecorator and PolyArgDecoratorMeta every-time. The only code you have to personally write something like as follows, which is fairly short:

from PolyArgDecoratorMeta import PolyArgDecoratorMeta
import time
class Wait(metaclass=PolyArgDecoratorMeta):
 def __init__(i, func, delay = 2):
     i._func = func
     i._delay = delay

 def __call__(i, *args, **kwargs):
     time.sleep(i._delay)
     r = i._func(*args, **kwargs)
     return r
Toothpick Anemone
  • 4,290
  • 2
  • 20
  • 42
3

the decorator with arguments should return a function that will take a function and return another function you can do that

def decorator_factory(argument):
    def decorator(function):
        def wrapper(*args, **kwargs):
            """
                add somhting
            """
            return  function(*args, **kwargs)
        return wrapper
    return decorator

or you can use partial from functools module

def decorator(function =None,*,argument ):
        if function is None :
            return partial(decorator,argument=argument)
        def wrapper(*args, **kwargs):
            """
                add somhting
            """
            return  function(*args, **kwargs)
        return wrapper

in the second option just make sure you pass the arguments like this :

@decorator(argument = 'args')
def func():
    pass
Belhadjer Samir
  • 1,461
  • 7
  • 15
2

It is a decorator that can be called in a variety of ways (tested in python3.7):

import functools


def my_decorator(*args_or_func, **decorator_kwargs):

    def _decorator(func):

        @functools.wraps(func)
        def wrapper(*args, **kwargs):

            if not args_or_func or callable(args_or_func[0]):
                # Here you can set default values for positional arguments
                decorator_args = ()
            else:
                decorator_args = args_or_func

            print(
                "Available inside the wrapper:",
                decorator_args, decorator_kwargs
            )

            # ...
            result = func(*args, **kwargs)
            # ...

            return result

        return wrapper

    return _decorator(args_or_func[0]) \
        if args_or_func and callable(args_or_func[0]) else _decorator


@my_decorator
def func_1(arg): print(arg)

func_1("test")
# Available inside the wrapper: () {}
# test


@my_decorator()
def func_2(arg): print(arg)

func_2("test")
# Available inside the wrapper: () {}
# test


@my_decorator("any arg")
def func_3(arg): print(arg)

func_3("test")
# Available inside the wrapper: ('any arg',) {}
# test


@my_decorator("arg_1", 2, [3, 4, 5], kwarg_1=1, kwarg_2="2")
def func_4(arg): print(arg)

func_4("test")
# Available inside the wrapper: ('arg_1', 2, [3, 4, 5]) {'kwarg_1': 1, 'kwarg_2': '2'}
# test

PS thanks to user @norok2 - https://stackoverflow.com/a/57268935/5353484

UPD Decorator for validating arguments and/or result of functions and methods of a class against annotations. Can be used in synchronous or asynchronous version: https://github.com/EvgeniyBurdin/valdec

Evgeniy_Burdin
  • 627
  • 5
  • 14
2

Here is a Flask example using decorators with parameters. Suppose we have a route '/user/name' and we want to map to his home page.

def matchR(dirPath):
    def decorator(func):
        def wrapper(msg):
            if dirPath[0:6] == '/user/':
                print(f"User route '{dirPath}' match, calling func {func}")
                name = dirPath[6:]
                return func(msg2=name, msg3=msg)
            else:
                print(f"Input dirPath '{dirPath}' does not match route '/user/'")
                return
        return  wrapper
    return decorator

#@matchR('/Morgan_Hills')
@matchR('/user/Morgan_Hills')
def home(**kwMsgs):
    for arg in kwMsgs:
        if arg == 'msg2':
            print(f"In home({arg}): Hello {kwMsgs[arg]}, welcome home!")
        if arg == 'msg3':
            print(f"In home({arg}): {kwMsgs[arg]}")

home('This is your profile rendered as in index.html.')

Output:

User route '/user/Morgan_Hills' match, calling func <function home at 0x000001DD5FDCD310>
In home(msg2): Hello Morgan_Hills, welcome home!
In home(msg3): This is your profile rendered as in index.html.
Leon Chang
  • 669
  • 8
  • 12
2

This is a great use case for a curried function.

Curried functions essentially delay a function from being called until all inputs have been supplied.

This can be used for a variety of things like wrappers or functional programming. In this case lets create a wrapper that takes in inputs.

I will use a simple package pamda that includes a curry function for python. This can be used as a wrapper for other functions.

Install Pamda:

pip install pamda

Create a simple curried decorator function with two inputs:

@pamda.curry()
def my_decorator(input, func):
    print ("Executing Decorator")
    print(f"input:{input}")
    return func

Apply your decorator with the first input supplied to your target function:

@my_decorator('Hi!')
def foo(input):
    print('Executing Foo!')
    print(f"input:{input}")

Execute your wrapped function:

x=foo('Bye!')

Putting everything together:

from pamda import pamda

@pamda.curry()
def my_decorator(input, func):
    print ("Executing Decorator")
    print(f"input:{input}")
    return func

@my_decorator('Hi!')
def foo(input):
    print('Executing Foo!')
    print(f"input:{input}")

x=foo('Bye!')

Would give:

Executing Decorator
input:Hi!
Executing Foo!
input:Bye!
conmak
  • 1,200
  • 10
  • 13
2

For example, I created multiply() below which can accept one or no argument or no parentheses from the decorator and I created sum() below:

from numbers import Number

def multiply(num=1):
    def _multiply(func):
        def core(*args, **kwargs):
            result = func(*args, **kwargs)
            if isinstance(num, Number):
                return result * num
            else:
                return result
        return core
    if callable(num):
        return _multiply(num)
    else:
        return _multiply

def sum(num1, num2):
    return num1 + num2

Now, I put @multiply(5) on sum(), then called sum(4, 6) as shown below:

# (4 + 6) x 5 = 50

@multiply(5) # Here
def sum(num1, num2):
    return num1 + num2

result = sum(4, 6)
print(result)

Then, I could get the result below:

50

Next, I put @multiply() on sum(), then called sum(4, 6) as shown below:

# (4 + 6) x 1 = 10

@multiply() # Here
def sum(num1, num2):
    return num1 + num2
    
result = sum(4, 6)
print(result)

Or, I put @multiply on sum(), then called sum(4, 6) as shown below:

# 4 + 6 = 10

@multiply # Here
def sum(num1, num2):
    return num1 + num2
    
result = sum(4, 6)
print(result)

Then, I could get the result below:

10
Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
1

define this "decoratorize function" to generate customized decorator function:

def decoratorize(FUN, **kw):
    def foo(*args, **kws):
        return FUN(*args, **kws, **kw)
    return foo

use it this way:

    @decoratorize(FUN, arg1 = , arg2 = , ...)
    def bar(...):
        ...
chen.wq
  • 31
  • 3
1

Suppose you have a function

def f(*args):
    print(*args)

And you want to add a decorator that accepts arguments to it like this:

@decorator(msg='hello')
def f(*args):
    print(*args)

This means Python will modify f as follows:

f = decorator(msg='hello')(f)

So, the return of the part decorator(msg='hello') should be a wrapper function that accepts the function f and returns the modified function. then you can execute the modified function.

def decorator(**kwargs):
    def wrap(f):
        def modified_f(*args):
            print(kwargs['msg']) # use passed arguments to the decorator
            return f(*args)
        return modified_f
    return wrap

So, when you call f, it is like you are doing: decorator(msg='hello')(f)(args) === wrap(f)(args) === modified_f(args) but modified_f has access to kwargs passed to the decorator

The output of

f(1,2,3)

will be:

hello
(1, 2, 3)
Mohamed Akram
  • 416
  • 4
  • 12
0

I think a working, real-world example, with usage examples of the most generic use-case can be valuable here.


The following is a decorator for functions, which prints to log upon entering and exiting the function.

Parameters control weather or not to print input and output values, log level and so on.

import logging 
from functools import wraps


def log_in_out(logger=logging.get_logger(), is_print_input=True, is_print_output=True, is_method=True, log_level=logging.DEBUG):
    """
    @param logger-
    @param is_print_input- toggle printing input arguments
    @param is_print_output- toggle printing output values
    @param is_method- True for methods, False for functions. Makes "self" not printed in case of is_print_input==True
    @param log_level-

    @returns- a decorator that logs to logger when entering or exiting the decorated function.
    Don't uglify your code!
    """

    def decor(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            if is_print_input:
                logger.log(
                    msg=f"Entered {fn.__name__} with args={args[1:] if is_method else args}, kwargs={kwargs}",
                    level=log_level
                )
            else:
                logger.log(
                    msg=f"Entered {fn.__name__}",
                    level=log_level
                )

            result = fn(*args, **kwargs)

            if is_print_output and result is not None:
                logger.log(
                    msg=f"Exited {fn.__name__} with result {result}",
                    level=log_level,
                )
            else:
                logger.log(
                    msg=f"Exited {fn.__name__}",
                    level=log_level
                )

            return result

        return wrapper

    return decor

usage:

 @log_in_out(is_method=False, is_print_input=False)
    def foo(a, b=5):
        return 3, a

foo(2) --> prints

Entered foo
Exited foo with result (3, 2)

    class A():
        @log_in_out(is_print_output=False)
        def bar(self, c, m, y):
            return c, 6

a = A() a.bar(1, 2, y=3) --> prints

Entered bar with args=(1, 2), kwargs={y:3}
Exited bar

Gulzar
  • 23,452
  • 27
  • 113
  • 201
-1

In case both the function and the decorator have to take arguments you can follow the below approach.

For example there is a decorator named decorator1 which takes an argument

@decorator1(5)
def func1(arg1, arg2):
    print (arg1, arg2)

func1(1, 2)

Now if the decorator1 argument has to be dynamic, or passed while calling the function,

def func1(arg1, arg2):
    print (arg1, arg2)


a = 1
b = 2
seconds = 10

decorator1(seconds)(func1)(a, b)

In the above code

  • seconds is the argument for decorator1
  • a, b are the arguments of func1
SuperNova
  • 25,512
  • 7
  • 93
  • 64
-1

Decoration with parameters in an anonymous setting.

Among of the many possibilities two variations of a "nested" syntactic sugar decoration are presented. They differ from each other by the order of execution wrt to the target function and their effects are, in general, independent (non interacting).

The decorators allow an "injection" a of custom function either before or after the execution of the target function.

The calls of both functions take place in a tuple. As default, the return value is the result of the target function.

The syntactic sugar decoration @first_internal(send_msg)('...end') required version >= 3.9, see PEP 614 Relaxing Grammar Restrictions On Decorators.

Used functools.wraps to keep the doc-string of the target function.

from functools import wraps


def first_external(f_external):
    return lambda *args_external, **kwargs_external:\
           lambda f_target: wraps(f_target)(
               lambda *args_target, **kwargs_target:
                  (f_external(*args_external, **kwargs_external),
                   f_target(*args_target, **kwargs_target))[1]
           )


def first_internal(f_external):
    return lambda *args_external, **kwargs_external:\
           lambda f_target: wraps(f_target)(
               lambda *args_target, **kwargs_target:
                  (f_target(*args_target, **kwargs_target),
                   f_external(*args_external, **kwargs_external))[0]
           )


def send_msg(x):
   print('msg>', x)


@first_internal(send_msg)('...end')    # python >= 3.9
@first_external(send_msg)("start...")  # python >= 3.9
def test_function(x):
    """Test function"""
    print('from test_function')
    return x


test_function(2)

Output

msg> start...
from test_function
msg> ...end

Remarks

  • composition decorators, such as pull-back and push-forward (maybe in a more Computer Science terminology: co- and resp. contra-variant decorator), could more useful but need ad-hoc care, for example composition rules, check which parameters go where, etc

  • syntactic sugar acts as a kind of partial of the target function: once decorated there is no way back (without extra imports) but it is not mandatory, a decorator can be used also in its extended forms, i.e. first_external(send_msg)("start...")(test_function)(2)

  • the results of a workbench with timeit.repeat(..., repeat=5, number=10000) which compare the classical def and lambda decoration shows that are almost equivalent:

    • for lambda: [6.200810984999862, 6.035239247000391, 5.346362481000142, 5.987880147000396, 5.5331550319997405] - mean -> 5.8206

    • for def: [6.165001932999985, 5.554595884999799, 5.798066574999666, 5.678178028000275, 5.446507932999793] - mean -> 5.7284

  • naturally an non-anonymous counterpart is possible and provides more flexibility

cards
  • 3,936
  • 1
  • 7
  • 25
  • Showing that functions can be rewritten as lambdas doesn't really add anything interesting to the solution; it's incidental to the important techniques needed for making the decorator accept parameters. – Karl Knechtel Oct 24 '22 at 00:07
  • @Karl Knechtel No one mentioned a `lambda` approach and I found it curious. Further, I showed that _syntactic sugar_ supports multiple calls and that one can gather abstraction by letting a parameter be a function again – cards Oct 24 '22 at 00:27
  • 1
    Because "a lambda approach" is *fundamentally not a different approach*. – Karl Knechtel Oct 24 '22 at 00:52