606

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
vinzee
  • 17,022
  • 14
  • 42
  • 60
falek.marcin
  • 8,408
  • 8
  • 27
  • 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

20 Answers20

1012

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
  • 10,733
  • 7
  • 77
  • 79
t.dubrownik
  • 10,982
  • 1
  • 16
  • 6
  • Thanks, your solution is more suited to my problem - Easily explains how to create decorators with the parameters – falek.marcin May 08 '11 at 18:15
  • 2
    I just did this with lambdas all over the place. (read: Python is awesome!) :) – Alois Mahdal Jun 06 '13 at 15:36
  • 107
    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
  • 5
    > 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
  • 35
    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
  • 11
    You forgot about return when calling function, i.e. `return function(*args, **kwargs)` – formiaczek Dec 01 '15 at 17:09
  • 64
    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
  • 1
    Thanks, years late but this solved my problem. Finally I'm feeling that I'm grasping the idea of decorators. – Nicholas Humphrey May 20 '19 at 16:37
  • 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
  • 1
    @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
  • This helps me understand how Flask's @app.route(path) maps to or calls its view function. Thanks. – Leon Chang Apr 22 '21 at 04:05
457

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
  • 8,823
  • 2
  • 22
  • 27
  • 7
    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
  • 10
    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
    The nicest explanation I've ever seen! – Cho Jul 25 '19 at 06:41
  • Thank you for that explanation. The link to the pycon talk was very helpful too. – Radha Satam Mar 13 '20 at 16:42
  • 1
    Perfect explanation. This is what every learner to Python decorators is looking for. – Ahmed Gad Apr 26 '20 at 08:10
  • 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
  • The video was great. Thanks! – Lee Loftiss Sep 12 '20 at 19:05
  • 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
  • Right. Thank you. @srj – jouell Sep 17 '21 at 01:48
  • @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
115

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
  • 21,459
  • 16
  • 82
  • 123
Ross R
  • 7,913
  • 7
  • 27
  • 26
  • 3
    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
  • 1
    this is my favorite answer here as wraps is critical. – Tommy May 23 '22 at 15:35
114

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,030
  • 21
  • 100
  • 124
Dacav
  • 12,630
  • 9
  • 57
  • 84
  • 4
    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
42

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,030
  • 21
  • 100
  • 124
Ross Rogers
  • 22,451
  • 24
  • 104
  • 161
  • 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
32

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.

Shital Shah
  • 55,892
  • 12
  • 218
  • 175
21
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,280
  • 24
  • 25
  • 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
16

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
  • 794
  • 5
  • 8
  • 2
    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
14

This is a template for a function decorator that does not require () if no parameters are to be given:

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
norok2
  • 21,682
  • 3
  • 59
  • 88
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ć
  • 3,972
  • 1
  • 30
  • 29
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
  • 41
  • 2
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
Samuel Muldoon
  • 1,224
  • 3
  • 17
4

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,774
  • 2
  • 25
  • 33
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
  • 429
  • 4
  • 11
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
  • 507
  • 4
  • 10
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
  • 867
  • 7
  • 12
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
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
  • 17,272
  • 18
  • 86
  • 144
0

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,270
  • 5
  • 14
-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
  • 21,204
  • 6
  • 80
  • 55