12 Steps to Understanding Python Decorators

It's an old article, but it was a good article to grasp the concept of Python decorators, so I translated it Japanese </ del>. http://simeonfranklin.com/blog/2012/jul/1/python-decorators-in-12-steps/

Step1. Function

>>> def foo():
...     return 1
...
>>> foo()
1

This is basic. In Python, a function can be defined with a function name and a list of parameters (optional) with the def keyword, and can be executed by specifying the name in parentheses.

Step2. Scope

In Python, creating a function creates a new scope, in other words, each function has its own namespace.

Python also provides a built-in function that allows you to check these, and locals () returns the values in your local namespace in dictionary format.

>>> def foo(arg):
...     x = 10
...     print locals()
...
>>> foo(20)
{'x': 10, 'arg': 20}

You can also check the global namespace with globals () in the same way.

>>> y = 30
>>> globals()
{..., 'y': 30} #Other global variables automatically created by Python are displayed, but omitted

Step3. Variable resolution rules

Variable resolution rules in Python

--A new variable is always created in that namespace when it is created --References search within the same namespace, otherwise expand the search outwards

Let's look at variables in global scope in the local scope of the function.

>>> text = "I am global!"
>>> def foo():
...     print text #1
...

>>> foo()
I am global!

The contents of the text defined outside the function are displayed properly. \ # 1 first looks for a local variable inside the function, and since it does not exist, it looks for a global variable named the same text.

Now let's change the variables defined outside the function inside the function.

>>> text = "I am global!"
>>> def foo():
...     text = "I am local!" #2
...     print locals()
...
>>> foo()
{'text': 'I am local!'}
>>> text
'I am global!'

When foo () is called, the value assigned in the function is set as the content of text, but the value of text of the outer global variable has not changed. \ # 2 actually looks for the global variable Instead of going to, a new local variable text is created in the function foo.

In other words, inside a function, global variables can be referenced but not assigned.

Step4. Variable lifetime

You need to know not only the scope but also the lifetime.

>>> def foo():
...     x = 1
...
>>> foo()
>>> print x #1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined

It's not just the scope problem mentioned above that is causing the error here. The namespace is created each time the function foo is called and disappears after processing. That is, in the above example, at the timing of \ # 1. It means that the variable x literally doesn't exist anywhere.

Step5. Function arguments and parameters

In Python you can give arguments to functions. The name of the parameter at definition is used as the name of the local variable.

>>> def foo(x):
...     print locals()
>>> foo(1)
{'x': 1} #Parameter x is used as the local variable name

Python offers a variety of ways to set parameters in a function and give arguments at call time. Parameters include required parameters and optional default parameters.

>>> def foo(x, y=0): # 1
...     return x - y
>>> foo(3, 1) # 2
2
>>> foo(3) # 3
3
>>> foo() # 4
Traceback (most recent call last):
  ...
TypeError: foo() takes at least 1 argument (0 given)
>>> foo(y=1, x=3) # 5
2

In the example of \ # 1, x is the parameter and y, which specifies the default value, is the default parameter. \ # 2 is a normal function usage example, but the default parameter is omitted as in # 3. It is also possible. The omitted argument takes the default value (0 in this case).

You can also use named arguments for Python, so you can specify the arguments without worrying about the order by specifying the name (\ # 4). If you understand that the arguments in Python are just dictionaries. It's a convincing move.

Step6. Function nesting

In Python you can define functions by nesting inside them.

>>> def outer():
...     x = 1
...     def inner():
...         print x # 1
...     inner() # 2
...
>>> outer()
1

The rules are the same as we've seen so far: \ # 1 looks for the local variable x but can't find it, so it looks for the outer namespace and refers to the x defined inside the outer, and \ # 2 So, we are calling inner (), but the important thing here is that ** inner is just one of the variable names, and it is called by looking for the definition in the namespace in outer based on the resolution rule ** about it.

Step7. Functions are first class objects in Python

In Python, the function itself is also just an object.

>>> issubclass(int, object) #All objects in Python are created by inheriting the same common class
True
>>> def foo():
...     pass
>>> foo.__class__
<type 'function'>
>>> issubclass(foo.__class__, object)
True

What this means is that you can treat a function as if it were any other general variable-that is, you can give it as an argument to another function or use it as a return value for a function.

>>> def add(x, y):
...     return x + y
>>> def sub(x, y):
...     return x - y
>>> def apply(func, x, y):
...     return func(x, y)
>>> apply(add, 2, 1)
3
>>> apply(sub, 2, 1)
1

In the above example, the function apply is designed to return the execution result of the function specified as a parameter, and you can see that the functions add and sub are given as arguments (just like other variables). Masu.

What about the following example?

>>> def outer():
...     def inner():
...         print "Inside inner"
...     return inner # 1
...
>>> foo = outer() #2
>>> foo
<function inner at 0x...>
>>> foo()
Inside inner

The difference from the apply example above is that \ # 1 specifies the function itself as the return value, not the execution result (given inner instead of inner ()).

You can see that this can be assigned normally, like \ # 2, and that foo contains a function that can be executed.

Step.8 Closure

Let's change the above example a little.

>>> def outer():
...     x = 1
...     def inner():
...         print x
...     return inner
>>> foo = outer()
>>> foo.func_closure
(<cell at 0x...: int object at 0x...>,)

inner is a function returned by outer, stored in foo and executable by foo () ... really?

I follow Python's variable resolution rules perfectly, but what about the life cycle? The variable x exists only while the outer function is being executed. Here, since the inner function is assigned to foo after the processing of the outer function is finished, isn't it possible to execute foo ()?

… Contrary to this expectation, foo () is feasible. This is because Python has the function of Function closure, which means that a function defined outside the __global scope (in this case, inner) remembers the information of the scope that surrounds it "at definition". You can check that it is actually remembered by calling the func_closure property as above.

Recall that we wrote "at definition". The inner function is newly defined each time the outer function is called.

>>> def outer(x):
...     def inner():
...         print x
...     return inner
>>> print1 = outer(1)
>>> print2 = outer(2)
>>> print1()
1
>>> print2()
2

In the above example, even if you do not directly enter a value as an argument to print1 or print2, each internal inner function remembers what value should be output. This is used to take a fixed argument. You can also generate a customized function like this.

Step.9 Decorator

It's finally time to talk about decorators. Based on the story so far, the conclusion is that a decorator is a "callable (*) that takes a function as an argument and returns a new function in exchange."

>>> def outer(some_func):
...     def inner():
...         print "before some_func"
...         ret = some_func() #1
...         return ret + 1
...     return inner
>>> def foo():
...     return 1
>>> decorated = outer(foo) #2
>>> decorated()
before some_func
2

Let's understand one by one. Here we define a function called outer that takes some_func as a parameter, and in outer we also define an internal function called inner. inner gets the value to return with \ # 1 after printing the string. Some_func can take different values each time outer is called, but here we just call whatever it is , Returns the value obtained by adding 1 to the execution result. Finally, the outer function returns the inner function itself.

In \ # 2, the return value of executing outer with foo as an argument is stored in the variable decorated as some_func. When foo is executed, 1 is returned, but in decorated with outer, 1 is added to it and 2 is returned. Decorated is, so to speak, a decoration version of foo (foo + processing something).

When you actually use a useful decorator, you often replace foo itself without preparing another variable like decorated, that is, as shown below.

>>> foo = outer(foo)

The original foo will no longer be called, and the decorated foo will always return. Let's look at a more practical example.

Suppose you're using a library that holds an object at a certain coordinate. That object holds x and y coordinate pairs, but unfortunately it doesn't have the ability to handle numbers such as addition and subtraction. But we Suppose that you need to do a lot of computation with this library, and you don't want to reorganize the source of the library. What should I do?

The approach is to create functions like add and sub as shown below.

>>> class Coordinate(object):
...     def __init__(self, x, y):
...         self.x = x
...         self.y = y
...     def __repr__(self):
...         return "Coord: " + str(self.__dict__)
>>> def add(a, b):
...     return Coordinate(a.x + b.x, a.y + b.y)
>>> def sub(a, b):
...     return Coordinate(a.x - b.x, a.y - b.y)
>>> one = Coordinate(100, 200)
>>> two = Coordinate(300, 200)
>>> add(one, two)
Coord: {'y': 400, 'x': 400}

What if, for example, a check process such as "0 must be the lower limit of the coordinate system to be handled" is required? In other words, at present

>>> one = Coordinate(100, 200)
>>> two = Coordinate(300, 200)
>>> three = Coordinate(-100, -100)
>>> sub(one, two)
Coord: {'y': 0, 'x': -200}
>>> add(one, three)
Coord: {'y': 100, 'x': 0}

However, sub (one, two) wants to return (0, 0), and add (one, three) wants to return (100, 200). Check the lower limit of each function. It's possible, but let's use a decorator to unify the check process!

>>> def wrapper(func):
...     def checker(a, b):
...         if a.x < 0 or a.y < 0:
...             a = Coordinate(a.x if a.x > 0 else 0, a.y if a.y > 0 else 0)
...         if b.x < 0 or b.y < 0:
...             b = Coordinate(b.x if b.x > 0 else 0, b.y if b.y > 0 else 0)
...         ret = func(a, b)
...         if ret.x < 0 or ret.y < 0:
...             ret = Coordinate(ret.x if ret.x > 0 else 0, ret.y if ret.y > 0 else 0)
...         return ret
...     return checker
>>> add = wrapper(add)
>>> sub = wrapper(sub)
>>> sub(one, two)
Coord: {'y': 0, 'x': 0}
>>> add(one, three)
Coord: {'y': 200, 'x': 100}

It's the same as doing the previous foo = outer (foo), but we can apply a useful checking mechanism to the processing results of the parameters and functions.

Step.10 @ Applying the symbol

Python provides syntactic sugar with the @ symbol for describing decorators.

>>> add = wrapper(add)

Is the description

>>> @wrapper
... def add(a, b):
...     return Coordinate(a.x + b.x, a.y + b.y)

It can be described in the form of.

How about it? It's a fairly high level story to read this far and create useful decorators like classmethod and staticmethod yourself, but at least it's not that difficult to use decorators-just prefix them with @decoratorname. I hope you can think of it!

Step.11 \ * args and \ * \ * kwargs

The decorator wrapper I wrote above is useful, but it can only be applied to a limited number of functions with two parameters. This time it's fine, but what if you want to write a decorator that can be applied to more functions? Is not it? Python also has features to support this. For more information, read the official documentation (http://docs.python.jp/2/tutorial/controlflow.html#tut-arbitraryargs). However, you can accept any number of required parameters by adding * (asterisk) to the parameters when defining the function.

>>> def one(*args):
...     print args
>>> one()
()
>>> one(1, 2, 3)
(1, 2, 3)
>>> def two(x, y, *args):
...     print x, y, args
>>> two('a', 'b', 'c')
a b ('c',)

You can see that any parameter part is passed as a list. Also, if you add * to the argument at the time of calling instead of defining it, the argument that has already become a list or tuple is unpacked and applied to the fixed argument. Will do it.

>>> def add(x, y):
...     return x + y
>>> lst = [1,2]
>>> add(lst[0], lst[1]) #1
3
>>> add(*lst) #2 <- #Refers to exactly the same meaning as 1
3

There is also a notation ** (two asterisks). This corresponds to the dictionary format instead of the list.

>>> def foo(**kwargs):
...     print kwargs
>>> foo()
{}
>>> foo(x=1, y=2)
{'y': 2, 'x': 1}

** Using kwargs in a function definition means that "all parameters not explicitly specified are stored in a dictionary named kwargs". * Like args, it also supports unpacking when calling a function. doing.

>>> dct = {'x': 1, 'y': 2}
>>> def bar(x, y):
...     return x + y
>>> bar(**dct)
3

Step.12 Generic decorator

Let's write a decorator that outputs the function arguments to the log using the above function. For the sake of simplicity, the log output is printed to stdout.

>>> def logger(func):
...     def inner(*args, **kwargs): #1
...         print "Arguments were: %s, %s" % (args, kwargs)
...         return func(*args, **kwargs) #2
...     return inner

Notice that \ # 1 allows the inner function to take any number and format parameters, and \ # 2 allows it to be unpacked and passed as an argument. You can also apply the decorator logger to.

>>> @logger
... def foo1(x, y=1):
...     return x * y
>>> @logger
... def foo2():
...     return 2
>>> foo1(5, 4)
Arguments were: (5, 4), {}
20
>>> foo1(1)
Arguments were: (1,), {}
1
>>> foo2()
Arguments were: (), {}
2

For those who want to know more details

If you understand the last example, you know about decorators! If you don't, go back to the previous step and read it over and over again. If you want to learn more, read the wonderful essay below by Bruce Eckel.

--

(*) callable is a general term for callable objects that take arguments and return results. Not only function but also class, method, generator, etc. are callable.

Recommended Posts

12 Steps to Understanding Python Decorators
Steps to install python3 on mac
[Road to Intermediate] Understanding Python Properties
Updated to Python 2.7.9
Understanding Python Coroutine
Reintroduction to Python Decorators ~ Learn Decorators by Type ~
Steps to install Python environment on Ubuntu
About python decorators
Understanding python self
"Backport" to python 2
First steps to try Google CloudVision in Python
3 steps to put Python + mecab in yum only
Steps to develop a web application in Python
Steps to create a Twitter bot with python
How to install Python
PyQ ~ Python First Steps ~
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
Introduction to Python language
Introduction to OpenCV (python)-(2)
Note to daemonize python
Introducing Python 2.7 to CentOS 6.6
Connect python to mysql
Steps to install the latest Python on your Mac
Steps from installing Python 3 to creating a Django app
Connect to BigQuery with Python
[2020.8 latest] How to install Python
[python] Convert date to string
Post from Python to Slack
How to install Python [Windows]
Post to vim → Python → Slack
Introduction to Python Django (2) Win
To flush stdout in Python
Convert numpy int64 to python int
[Python] Convert list to Pandas [Pandas]
Cheating from PHP to Python
A road to intermediate Python
Try to understand Python self
Python notes to forget soon
[Python] How to use list 1
Login to website in Python
Connect to Wikipedia with Python
How to update Python Tkinter to 8.6
Post to slack with Python 3
Anaconda updated from 4.2.0 to 4.3.0 (python3.5 updated to python3.6)
Post to Twitter using Python
[GCP] Steps to deploy DataFlow on Cloud Shell (using Python)
How to use Python argparse
python-social-auth v0.2 to v0.3 migration steps
Start to Selenium using python
Introduction to serial communication [Python]
Update python on Mac to 3.7-> 3.8
3 Reasons Beginners to Start Python
Convert Scratch project to Python
Paiza Python Primer 8: Understanding Classes
[Python] Convert Shift_JIS to UTF-8
Python: How to use pydub
[Python] How to use checkio
Switch from python2.7 to python3.6 (centos7)