Decorators with parameters?

前端 未结 13 1424
我在风中等你
我在风中等你 2020-11-21 22:58

I have a problem with the transfer of variable \'insurance_mode\' by the decorator. I would do it by the following decorator statement:

@execute_complete_rese         


        
相关标签:
13条回答
  • 2020-11-21 23:23

    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.

    0 讨论(0)
  • 2020-11-21 23:28

    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.

    0 讨论(0)
  • 2020-11-21 23:28

    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
    0 讨论(0)
  • 2020-11-21 23:29

    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.

    0 讨论(0)
  • 2020-11-21 23:32

    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.

    0 讨论(0)
  • 2020-11-21 23:33

    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):
            #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 bar(*args, **kwargs):
        pass
    
    >>> print(bar)
    <function __main__.bar(*args, **kwargs)>
    
    0 讨论(0)
提交回复
热议问题