Here's a summary of decorators, which are notations that use the Python at sign (@). Decorators may seem difficult, but I thought it was a shortcut to understanding the types (patterns) of decorators, so this time I'll give a simple implementation example of decorators for each type. think.
--I've learned Python decorators, but sometimes I don't understand ――I'm worried if I'm asked to make my own decorator
--Function scope
--First-class function
--Notation like * args
, ** kwargs
It seems possible to classify decorator functions according to the following two factors.
--Whether or not to take an argument --Whether to return a wrapper function
Therefore, a total of four patterns can be considered from the combination, but this time we will introduce three except for "a decorator that takes no arguments and does not return a wrapper function". The reason for excluding one is too easy to make sense.
As a definition of the term, we'll call the function with the decorator (the function defined under @decorator
) ** the original function ** (is there a good name?)
def args_logger(f):
def wrapper(*args, **kwargs):
f(*args, **kwargs)
print('args: {}, kwargs: {}'.format(args, kwargs))
return wrapper
@args_logger
def print_message(msg):
print(msg)
#Equivalent to
'''
def print_message(msg):
print(msg)
print_message = args_logger(print_message)
'''
print_message('hello')
Execution result
hello
args: ('hello',), kwargs: {}
First of all, the one that is easiest to understand. The ʻargs_loggerfunction returns a wrapper function that prints () the argument information of the original function. In other words, the function with
@args_loggerwill spit out argument information each time it is executed. As in this example, it is customary to name the returned wrapper function
wrapper`.
funcs = []
def appender(*args, **kwargs):
def decorator(f):
#The processing content may or may not be changed depending on the content of args or kwargs.
print(args)
if kwargs.get('option1'):
print('option1 is True')
#Add original function to funcs
funcs.append(f)
return decorator
@appender('arg1', option1=True)
def hoge():
print('hoge')
@appender('arg2', option2=False)
def fuga():
print('fuga')
#Equivalent to
'''
def hoge():
print('hoge')
appender('arg1', option1=True)(hoge)
def fuga():
print('fuga')
appender('arg2', option2=False)(fuga)
'''
for f in funcs:
f()
Execution result
('arg1',)
option1 is True
('arg2',)
hoge
fuga
ʻAppenderThe decorator appends the original function to the funcs list. You can change the processing content depending on the arguments passed to the decorator function (I couldn't think of a good example this time, so I just printed it appropriately). Note that if you keep arguments in the decorator, you can't handle the original function
fdirectly inside the decorator function. Instead, define a "function that handles the original function". This function is customarily named
decorator (probably). Flask's ʻapp / Flask.route
function applies as a decorator for this pattern.
https://github.com/pallets/flask/blob/3a0ea726bd45280de3eb3042784613a676f68200/flask/app.py#L1222
It seems that this pattern is used simply to define a callback function like flask.
def args_joiner(*dargs, **dkwargs):
def decorator(f):
def wrapper(*args, **kwargs):
newargs = dargs + args #Join list
newkwargs = {**kwargs, **dkwargs} #Combine dictionaries(python3.Works with 5 or above)
f(*newargs, **newkwargs)
return wrapper
return decorator
@args_joiner('darg', dkwarg=True)
def print_args(*args, **kwargs):
print('args: {}, kwargs: {}'.format(args, kwargs))
#Equivalent to
'''
def print_args(*args, **kwargs):
print('args: {}, kwargs: {}'.format(args, kwargs))
print_args = args_joiner('darg', dkwarg=True)(print_args)
'''
print_args('arg', kwarg=False)
Execution result
args: ('darg', 'arg'), kwargs: {'kwarg': False, 'dkwarg': True}
It became more complicated with the feeling of a combination of the first and second examples. ʻArgs_joiner` The decorator returns a function that takes an argument that concatenates the arguments of the original function and the decorator (the process of concatenating the arguments has no deep meaning, just an example). Note that the nesting became deeper once when "returning a function returns a function". If you do your best to follow the process, you will know what you are doing.
Summarized through three examples. To understand what a decorator is doing, it's easy to see what kind of decorator it is.
For example, like the first example
def hoge_deco(func):
def wrapper(...):
...
return wrapper
Written like this, it would be nice to instantly know that the hoge_deco
decorator is ** no arguments ** decorator ** returns a wrapper function **. The judgment material is that func
is taken as an argument of hoge_deco
, and that the wrapper
function is defined and it is returned at the end.
Like the second example
def fuga_deco(*args, **kwargs):
def decorator(f):
# args, kwargs,Do something with f(Add f to a list, etc.)
...
return decorator
If it were written this way, think of the fuga_deco
decorator as a ** argument ** decorator that ** doesn't return a wrapper function **. The deciding factor is that the argument of fuga_deco
takes something other than a function, and that a function named decorator
that takes the function f
as an argument instead of the name wrapper
is defined. Is returning. Note that the decorator for this pattern does not return a wrapper function, so the original function itself does not change.
As in the third example
def piyo_deco(*dargs, **dkwargs):
def decorator(f):
...
def wrapper(*args, **kwargs):
...
return wrapper
return decorator
If it says, the piyo_deco
decorator is ** an argument ** decorator that returns a ** wrapper function **. Is it okay to make a decision?
What kind of decorator is there in this example? I can't think of it for a moment, but I think there are quite a few.
If you understand the decorator code, it will be easier to create your own decorator. I think you can write most of the three patterns this time, so first think about which pattern you need, and then mechanically write * arg, ** args
, def wrapper
, or def decorator
. If you do, you can be called a decorator master.
I've never made a decorator myself, but writing this article was a good review. I want to decorate bang bang from now on!
Recommended Posts