Reintroduction to Python Decorators ~ Learn Decorators by Type ~

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.

Target person

--I've learned Python decorators, but sometimes I don't understand ――I'm worried if I'm asked to make my own decorator

Good to know

--Function scope --First-class function --Notation like * args, ** kwargs

Classification of decorator functions

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?)

1. When returning a wrapper function with a no-argument decorator

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 functionwrapper`.

2. If the decorator with arguments does not return a wrapper function

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 functionfdirectly inside the decorator function. Instead, define a "function that handles the original function". This function is customarily nameddecorator (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.

3. When returning a wrapper function in a decorator with arguments

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.

Let's see what kind of decorator it is

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.

Let's be able to mount decorators smoothly

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

Reintroduction to Python Decorators ~ Learn Decorators by Type ~
Practice! !! Introduction to Python (Type Hints)
Learn Python by drawing (turtle graphics)
Easy Python to learn while writing
[Introduction to Udemy Python3 + Application] 28. Collective type
Answer to AtCoder Beginners Selection by Python3
[Introduction to Udemy Python3 + Application] 21. Tuple type
Function to save images by date [python3]
[Introduction to Udemy Python3 + Application] 24. Dictionary type
Recommended books by 3 types related to Python
[Introduction to Udemy Python3 + Application] 16. List type
Want to add type hints to your Python decorator?
[python] How to display list elements side by side
Updated to Python 2.7.9
[Python] How to use two types of type ()
Reintroduction to Docker
Python numeric type
Type Python scripts to run in QGIS Processing
How to erase the characters output by Python
About python decorators
How to get dictionary type elements of Python 2.7
Python2 string type
[Python] How to sort instances by instance variables
I want to sell Mercari by scraping python
Python # string type
About Python decorators
How to handle datetime type in python sqlite3
Execute Power Query by passing arguments to Python
[Python] Continued-Convert PDF text to CSV page by page
"Backport" to python 2
Learn python gestures
[Keras] Personal memo to classify images by folder [Python]
How to convert Python # type for Python super beginners: str
List of posts related to optimization by Python to docker
The first algorithm to learn with Python: FizzBuzz problem
[Reintroduction to python] How to import via the parent directory
How to write a list / dictionary type of Python3
[Python] Convert PDF text to CSV page by page (2/24 postscript)
Read the xml file by referring to the Python tutorial
Python --Notes when converting from str type to int type
How to save a table scraped by python to csv
Python # How to check type and type for super beginners
Convert Webpay Entity type to Dict type (recursively in Python)
Learn Python asynchronous processing / coroutines by comparing with Node.js
python> datetime> From date string (ISO format: 2015-12-09 12:40:08) to datetime type
Primality test by Python
How to install Python
Learn Python with ChemTHEATER
Visualization memo by Python
Communication processing by Python
Python callable type specification
Changes from Python 3.0 to Python 3.5
Changes from Python 2 to Python 3.0
Rewrite Python2 code to Python3 (2to3)
How to install python
python decorator to retry
Introduction to Python language
Introduction to OpenCV (python)-(2)
Note to daemonize python
Introducing Python 2.7 to CentOS 6.6
Beamformer response by python