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 the开发者_开发问答re 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
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.
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
Here is a slightly modified version of t.dubrownik's answer. Why?
- As a general template, you should return the return value from the original function.
- This changes the name of the function, which could affect other decorators / code.
So use @functools.wraps()
:
from functools import wraps
def create_decorator(argument):
def decorator(function):
@wraps(function)
def wrapper(*args, **kwargs):
funny_stuff()
something_with_argument(argument)
retval = function(*args, **kwargs)
more_funny_stuff()
return retval
return wrapper
return decorator
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.
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.
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.
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
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)
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"
- 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.
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
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.
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 Wait
which 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
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
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.
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!
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
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(...):
...
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
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)
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
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 fordecorator1
a, b
are the arguments offunc1
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 classicaldef
andlambda
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
精彩评论