About Python decorators

This time I'm going to talk about Python decorators.

Introduction.

While studying along with expert Python programming [^ expert_python], the concept of a Python decorator came up. I couldn't understand what I was saying in the contents of the book, so I did a lot of research.

[^ expart_python]: Expert Python Programming Revised 2nd Edition was released on February 26, 2018. For those who are studying from now on, the 2nd edition is recommended.

What is a decorator?

Decorate is a qualification. In a nutshell, a decorator is a function and its mechanism for modifying a function. For example, suppose you have a function. Let's take a look at the following test () function and its execution script.

Sample script 1

test.py


def test():
    print('Hello Decorator')

test()

The execution result is as follows.

Hello Decorator

Well, it's natural.

We will decorate this test function.

A simple example of a decorator

Here's what the above script is decorated with a decorator:

sample_deco1.py


def deco(func):
    def wrapper(*args, **kwargs):
        print('--start--')
        func(*args, **kwargs)
        print('--end--')
    return wrapper

@deco
def test():
    print('Hello Decorator')

test()

The execution result is as follows.

--start--
Hello Decorator
--end--

Now let's see what this is going on.

A brief description of Nakami

The 1st line def deco (func): to the 6th line return wrapper is the decorator definition function for decorating. Decorate according to this function. The 8th line @ deco decorates the following function with the decorator definition function" deco ". It will be.

Now let's talk about the contents of the decorator's definition function. The important thing is the 3rd line print ('--start--') to the 5th line print ('--end--').

Line 1 def deco (func): is the declaration of the decorator function. Although func is specified as an argument, the function to be decorated, test () itself, is passed as an argument.

Line 2 def wrapper (* args, ** kwargs): is a function to write the contents of the actual decorator. If you don't understand well, let's proceed while grasping this kind of thing. I will explain in detail later.

Now, the important 3rd to 5th lines. The fourth line, func (* args, ** kwargs), executes the function passed as an argument. In the 3rd line print ('--start--') and the 6th line print ('--end--'), the 5th line func (* args, ** kwargs) Indicates the processing to be executed before and after.

So, * args, ** kwargs are the arguments of the function to decorate. Well, it's okay if you write any function like this without thinking about it (maybe)

So what can you do with a decorator?

By using the decorator, you can add processing by yourself before and after processing the existing function. I thought about what kind of situation to do such a thing. I myself haven't learned much yet, but by using a decorator, I can add a process that is automatically executed when a function provided as a library is called. I will.

In Python, it is possible to rewrite the function itself in the form of an override used in Java etc. However, there is no problem with the existing processing, but I also want to do different processing at the same time. I think you use decorators in situations like this.

When I was researching, I was introduced to a program that uses a decorator to benchmark a function.

Well, once you have created one decorator function for the benchmark, you can measure each benchmark just by decorating various functions with @. It's convenient.

Example decorator in a method with a return value

In the above example, the method being decorated had no return value. But, of course, methods sometimes have a return value. Here is an example of a decorator for a method that has a return value.

sample_deco2.py


def deco2(func):
    import os
    def wrapper(*args,**kwargs):
        res = '--start--' + os.linesep
        res += func(*args,**kwargs) + '!' + os.linesep
        res += '--end--'
        return res
    return wrapper

@deco2
def test2():
    return('Hello Decorator')

print(test2())

The execution result of this script is as follows.

--start--
Hello Decorator!
--end--

Ah! ?? The execution result hasn't changed! !! What? No, it has changed slightly. Part of the output has changed from Hello Decorator toHello Decorator!. The contents of the program have changed considerably. I will explain this program while comparing it with the previous program.

First, compared to the previous program, the decorated method def test2 (): now has a string return value return'Hello Decorator'. And print (test2 ()) prints the returned string.

Earlier, the method to be decorated was a method to output a character string, but this time the method to be decorated is a method to generate a character string. If you decorate a method that produces something, you must write the decorator so that the generated result is returned.

Pick up a different place from the contents of the decorator. Line 5 res = func (* args, ** kwargs) +'!' Line 7 return res Line 8 return wrapper

In the 5th line, func (** kwargs) is executed and '!' Is added to the end of the returned character string and stored in res. Line 7 returns res. We are returning it to the method that is further decorated with the return wrapper on line 8.

What does that mean

Decorators mean that not only can you decorate a method that returns a value, but you can also process the return value of the method you're decorating. This is very interesting. If you think it's possible to add certain processing to the output of the decorating program ...? It will be a lot of fun.

Furthermore, for example, notations enclosed in tags such as html and xml can be described by a method that generates only the contents by using a decorator.

that? For example, if you write html with a decorator, you want to nest it ... Yes. Decorators can also be nested.

Nest decorators

It is also possible to combine multiple decorators. For example, the situation is as follows.

deco_sample3.py


def deco_html(func):
    def wrapper(*args, **kwargs):
        res = '<html>'
        res = res + func(*args, **kwargs)
        res = res + '</html>'
        return res
    return wrapper

def deco_body(func):
    def wrapper(*args, **kwargs):
        res = '<body>'
        res = res + func(*args, **kwargs)
        res = res + '</body>'
        return res
    return wrapper

@deco_html
@deco_body
def test():
    return 'Hello Decorator'

print(test())

The execution result is as follows.

<html><body>Hello Decorator!</body></html>

This program is not difficult if you know what you have done so far. There are only two decorators to decorate def test (str):. It will be processed in order from the one below.

It's getting pretty fun. It seems that decorators can do various things. that? By the way, isn't it possible to use a decorator for a method that has arguments? I can do it.

Decorate a method with arguments

Create the decoration of the method with arguments as follows.

deco_sample4.py


def deco_p(func):
    def wrapper(*args, **kwargs):
        res = '<p>'
        res = res + func(args[0], **kwargs)
        res = res + '</p>'
        return res
    return wrapper

@deco_p
def test(str):
    return str

print(test('Hello Decorator!'))

The execution result is as follows.

<p>Hello Decorator!</p>

By the way, what is the difference from the previous program? It is func (args [0], ** kwargs). In fact, the decorator is ready to accept arguments, regardless of whether the method you are decorating has arguments. If you want to use arguments, take the arguments from args and pass them to func. And what's interesting is that args is a tuple.

What does tuple mean? For the time being, at least the built-in method len () can be used. This means that you can change the behavior of the decorator depending on the number of arguments. I wonder if I can pass it as an argument to the decorator in the first place ... You can pass it.

Pass arguments to the decorator

deco_sample5.py


def deco_tag(tag):
    def _deco_tag(func):
        def wrapper(*args, **kwargs):
            res = '<'+tag+'>'
            res = res + func(*args, **kwargs)
            res = res + '</'+tag+'>'
            return res
        return wrapper
    return _deco_tag

@deco_tag('html')
@deco_tag('body')
def test():
    return 'Hello Decorator!'

print(test())

The output result is as follows.

<html><body>Hello Decorator!</body></html>

I'm not going to get tired of it, but here I'm nesting decorators.

end

This time, I tried various decorators and wrote their contents here. Decorators are interesting. I think I can do various things. For example, if you combine this with a generator ... it seems that you can really do various things. The reference URL I wrote at the very beginning has a more detailed explanation of the decorator, so if you are interested, please.

See you soon.

Postscript 2018.7.6

I wrote this article in January 2015. It's been three and a half years since it was published, and I'm grateful that there are still people who like the article. After three and a half years, I also have a better understanding of Python decorators.

I will add a story that can be done now.

Another example

I didn't think of it at the time, but now I'll show you how to use it with a decorator. (I don't know if I will actually do it)

For example, if you have the following modules:

my_method.py


def my_method(*args, **kwargs):
    if 'test_key' in kwargs:
        print('test_The value of key is[{}]'.format(kwargs['test_key']))
    else:
        print('test_There is no value in key.')

my_method()
my_method(test_key="Value for testing")

The execution result of the module is as follows.

test_There is no value in key.
test_The value of key is[Value for testing]

You can change the behavior all at once by using the decorator.

my_method_2.py


def my_decorator(target_function):
    def wrapper_function(*args, **kwargs):
        kwargs['test_key'] = 'Value rewritten by the decorator'
        return target_function(*args, **kwargs)
    return wrapper_function

@my_decorator
def my_method(*args, **kwargs):
    if 'test_key' in kwargs:
        print('test_The value of key is[{}]'.format(kwargs['test_key']))
    else:
        print('test_There is no value in key.')

my_method()
my_method(test_key="Value for testing")

The execution result is as follows.

test_The value of key is[Value rewritten by the decorator]
test_The value of key is[Value rewritten by the decorator]

You can change the arguments all at once like this. What do you use it for? I don't get a good example to answer that, but I think it's good to know that you can do this as well.

Decorator is syntactic sugar

The decorator is syntactic sugar. Syntactic sugar is a grammar designed to make it easier to write programs for certain purposes. Not limited to Python, there are syntactic sugar grammars in other languages as well.

Being syntactic sugar means that there is another way to write it.

For example, if you have the following modules:

def twice(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs) * 2
    return wrapper

@twice
def add(x, y):
    return x + y

print(add(1, 3))

The result of this execution is the same as the result of the next execution.

def twice(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs) * 2
    return wrapper

def add(x, y):
    return x + y

print(twice(add)(1, 3))

I passed the add function to the function called twice and passed 1, 3 to the function that came out and printed the result of execution.

It's hard to understand what you want to do if you write it like this. That's why I think that the decorator has been created.

Reference URL

--Python official documentation: https://docs.python.jp/3/glossary.html#term-decorator

Recommended Posts

About python decorators
About Python decorators
About python slices
About python comprehension
About Python tqdm.
About python yield
About python, class
About python inheritance
About python, range ()
[Python] About multi-process
About Python for loops
Summary about Python scraping
[Python] Memo about functions
Summary about Python3 + OpenCV3
About Python, for ~ (range)
About Python3 character code
[Python] Memo about errors
About Python development environment
Python: About function arguments
Python, about exception handling
About Python Pyramid traversal
About Python3 ... (Ellipsis object)
[Python] Chapter 01-01 About Python (First Python)
[Python] About standard input
About __all__ in python
[Python] Find out about pip
About Fabric's support for Python 3
Python
About python objects and classes
About Python variables and objects
About the Python module venv
Think about architecture in python
About python beginner's memorandum function
About the ease of Python
About the enumerate function (python)
About various encodings of Python 3
About Python, len () and randint ()
About Perl, Python, PHP, Ruby
About Python datetime and timezone
A memorandum about correlation [Python]
A memorandum about Python mock
About Python string comparison operators
About Python and regular expressions
About the features of Python
About "for _ in range ():" in python
About Python and os operations
Python # About reference and copy
About Python sort () and reverse ()
A note about [python] __debug__
Initializing global variables using Python decorators
Python Note: About comparison using is
About installing Pwntools and Python2 series
Python: A Note About Classes 1 "Abstract"
[Python] Let's write briefly about comprehensions
About python dict and sorted functions
[Python] What is @? (About the decorator)
What was surprising about Python classes
[Python] What are @classmethods and decorators?
[Python] About Executor and Future classes
About Python, from and import, as
I learned about processes in Python