Home >Backend Development >Python Tutorial >Detailed explanation of Python decorators

Detailed explanation of Python decorators

高洛峰
高洛峰Original
2016-12-01 15:55:172208browse

The decorator in python is a very commonly used thing. We can write some specific methods and general methods as decorators. This provides a great convenience for calling these methods, thus improving the efficiency of our code. Readability and simplicity, as well as scalability.

Before learning python decorators, let’s take a look at this example:

1. Scope

# coding:utf-8
 
msg = 'hello test1'
 
 
def add():
    msg = 'this is add'
    print msg     #当执行add()时将打印'this is add'
def add2():
    print msg     #当执行add2()时将打印'hello test1'

The above example simply explains the scope of python. The example declares the global variable msg and the add function. A local variable msg is also declared in. When the "print msg" of add() is executed, it will first find whether there is a local variable msg in add. If it is not found, it will go to the upper level scope to find whether the variable exists. The local variable is in Generated when the function is running, local variables are also destroyed when the function finishes running. Next, let’s deepen the above example:

2. Closure

# coding:utf-8
 
def add():
    msg = 'hello add'
    def inner():
        print msg               #运行add()这里会打印'hello add'
    return inner

>>> obj = add()
>>> obj            #可见obj是一个指向inner函数的对象
<function inner at 0x0000000002855438>
...
>>> obj()
hello add           #运行obj()打印msg

After reading the above example, are there any points? Confused, obj is the object pointing to the inner function. When running obj, it is equivalent to running inner, but the add function does not run, which means that msg is not declared and inner does not declare the variable msg. How does it find the variable msg? What's the value?

  This is "closure" in python. Python supports a feature called function closure. In human terms, a function nested in a non-global scope can remember where it was when it was defined. enclosed namespace. This can be concluded by looking at the obj.func_closure attribute of the function. This attribute contains the value in the closed scope (only the captured value will be included. If other values ​​​​are defined in add, the closed scope is No) Closure is the core principle of python decorators. Next, let’s write a simple decorator example:

3. Simple decorator

# coding:utf-8
 
def check_args_num(func):
    # 该装饰器用于检查传入的参数数量,检查是否是两个参数
    def inner(*args, **kwargs):
        args_list = list(args)
        if len(args_list) < 2:
 
            for i in range(2 - len(args)):
                # 如果参数数量小于2则用0填补
                args_list.append(0)
        if len(args_list) > 2:
            # 如果参数数量大于2则打印错误
            print &#39;The args number is too many!&#39;
        func(*args_list, **kwargs)
    return inner
 
 
@check_args_num
def add(x, y):
    return x + y

Execution result:

>>>print add(1,2)
3
...
>>>print add(100)
100
...
>>>print add(1,2,3)
Traceback (most recent call last):
  File "D:\PyCharm 5.0.4\helpers\pydev\pydevd_exec.py", line 3, in Exec
    exec exp in global_vars, local_vars
  File "<input>", line 1, in <module>
  File "E:/code/my_project/decorator/test1.py", line 14, in inner
    raise Exception(&#39;The args number is too many!&#39;)
Exception: The args number is too many!
...
>>>add
<function inner at 0x0000000002A6C3C8>
#可以看到add函数现在指向的是inner

4. Multiple decorators

# coding:utf-8
 
def check_args_int(func):
    # 该装饰器用于检查传入的参数是否是int型
    def ensure_int(*args, **kwargs):
        from array import array
        try:
            array(&#39;H&#39;, args)
        except Exception, e:
            raise Exception(e)
        return func(*args, **kwargs)
 
    return ensure_int
 
 
def check_args_num(func):
    # 该装饰器用于检查传入的参数数量,检查是否是两个参数
    def inner(*args, **kwargs):
        args_list = list(args)
        if len(args_list) < 2:
 
            for i in range(2 - len(args)):
                # 如果参数数量小于2则用0填补
                args_list.append(0)
        if len(args_list) > 2:
            # 如果参数数量大于2则打印错误
            raise Exception(&#39;The args number is too many!&#39;)
        return func(*args_list, **kwargs)
 
    return inner
 
 
@check_args_num
@check_args_int
def add(x, y):
    return x + y

An extra decorator for parameter content checking is added here. When there are multiple decorators, they will be executed from top to bottom. Check_args_num is executed first and then check_args_int is executed. The execution result is:

>>> print add(1,&#39;fsaf&#39;)
Traceback (most recent call last):
  File "D:\PyCharm 5.0.4\helpers\pydev\pydevd_exec.py", line 3, in Exec
    exec exp in global_vars, local_vars
  File "<input>", line 1, in <module>
  File "E:/code/my_project/decorator/test1.py", line 28, in inner
    return func(*args_list, **kwargs)
  File "E:/code/my_project/decorator/test1.py", line 10, in ensure_int
    raise Exception(e)
Exception: an integer is required
...
>>> add
<function inner at 0x0000000002B1C4A8>

#Here you can see that add still points to inner. We can understand that when multiple decorators exist, when add is called, the call entry is always the first decorator. After the first decorator is executed, the next one will be executed. One, and the parameters will be passed on in sequence

5. Decorator with parameters

We know that when defining a decorator, the first parameter passed into the decorator is the decorated function (eg. example in add), sometimes we also need to pass additional parameters to the decorator. The following example will pass additional parameters to the decorator, as follows:

# coding:utf-8
 
def check_args_int(func):
    # 该装饰器用于检查传入的参数是否是int型
    def ensure_int(*args, **kwargs):
        from array import array
        try:
            array(&#39;H&#39;, args)
        except Exception, e:
            raise Exception(e)
        return func(*args, **kwargs)
 
    return ensure_int
 
 
def check_args_num(flag):
    &#39;&#39;&#39;
    :param func: 被装饰函数
    :param flag: 决定是否检查参数数量
    &#39;&#39;&#39;
 
    # 该装饰器用于检查传入的参数数量,检查是否是两个参数
    def get_func(func):
 
        def inner(*args, **kwargs):
            if flag == &#39;false&#39;:
                print &#39;Skip check !&#39;
                return func(*args, **kwargs)
            args_list = list(args)
            if len(args_list) < 2:
 
                for i in range(2 - len(args)):
                    # 如果参数数量小于2则用0填补
                    args_list.append(0)
            if len(args_list) > 2:
                # 如果参数数量大于2则打印错误
                raise Exception(&#39;The args number is too many!&#39;)
            return func(*args_list, **kwargs)
 
        return inner
 
    return get_func
 
 
@check_args_num(&#39;false&#39;)
@check_args_int
def add(x, y):
    return x + y

This example only has check_args_num that is different from the previous one. The difference is that The decorator check_args_num has an additional parameter flag. When flag=='false', the parameter number check is skipped. The following is the output result

>>>print add(1, 2)
Skip check !
3

6. Decorator Decorator without parameters

The decorator module is python A module used to specifically encapsulate decorators. It is easier to use decorator to construct decorators. At the same time, the signature of the decorated function remains unchanged
. Previously, we talked about implementing python decorator construction through closures. Here we use the decorator module to implement python decorators and their functions. The principles are the same

from decorator import decorator
 
 
@decorator
def check_num(func, *args, **kwargs):
    if len(args) != 2:
        raise Exception(&#39;Your args number is not two&#39;)
    else:
        return func(*args, **kwargs)
 
 
@check_num
def add(x, y):
    return x + y

>>> add
<function add at 0x0000000002D43BA8>
>>> print add(1,2)
3
...
>>> add(1,2,3)
Traceback (most recent call last):
  File "D:\PyCharm 5.0.4\helpers\pydev\pydevd_exec.py", line 3, in Exec
    exec exp in global_vars, local_vars
  File "<input>", line 1, in <module>
TypeError: add() takes exactly 2 arguments (3 given)
 
#可以看到这里当我们传三个参数给add()函数时,他直接从add()函数抛出类型错误异常,
#并没有进入check_num装饰器进行参数校验,可见被装饰的函数add()的签名还是他本身
#如果直接构造装饰器,那么这里将会从check_num里面抛出异常,如下:
 
def check_num(func):
    def inner(*args,**kwargs):
        if len(args) != 2:
            raise Exception(&#39;Your args number is not two&#39;)
        else:
            return func(*args,**kwargs)
    return inner
 
@check_num
def add(x, y):
    return x + y
 
 
>>> add
<function inner at 0x0000000002E233C8>
>>>add(1,2,3)
Traceback (most recent call last):
  File "D:\PyCharm 5.0.4\helpers\pydev\pydevd.py", line 2411, in <module>
    globals = debugger.run(setup[&#39;file&#39;], None, None, is_module)
  File "D:\PyCharm 5.0.4\helpers\pydev\pydevd.py", line 1802, in run
    launch(file, globals, locals)  # execute the script
  File "E:/code/my_project/decorator/test3.py", line 14, in <module>
    print add(1,2,3)
  File "E:/code/my_project/decorator/test3.py", line 4, in inner
    raise Exception(&#39;Your args number is not two&#39;)
Exception: Your args number is not two

As can be seen from the above example, when constructing a decorator through closure, its execution function entrance is a nested function in the decorator, so it may occur For the above problem, when add(1,2,3) is executed, the inner function will be executed first (if there is no parameter verification in the inner, no exception will be thrown here, only when return func(*args,* is executed) *kwargs), the add(x,y) function is actually called, and an exception is thrown at this time). This will cause the program to execute redundant code, wasting memory and CPU.

7. Decorator with parameters

What if you want the decorator to take parameters?

from decorator import decorator
 
 
def check(flag):
    @decorator
    def check_num(func, *args, **kwargs):
        if flag == &#39;false&#39;:
            print &#39;skip check !&#39;
            return func(*args,**kwargs)
        if len(args) != 2:
            raise Exception(&#39;Your args number is not two&#39;)
        else:
            return func(*args, **kwargs)
    return check_num
 
 
@check(&#39;false&#39;)
def add(x, y):
    return x + y
 
>>>add
<function add at 0x0000000002C53C18>
>>>add(1,2)
skip check !
3

That’s it for the decorator module. This module is relatively simple, and there are some functions not mentioned that can be seen at a glance by viewing the source code. The principles are all implemented using python closures

8. functools.wraps( func) decorator

The function of functools.wraps and decorator modules are the same, both are to solve the signature problem of the decorated function. Here is only an example with parameters for this kind of decorator construction method:

import functools
 
def check(flag):
    def wraps(func):
        @functools.wraps(func)
        def check_num(*args, **kwargs):
            if flag == &#39;false&#39;:
                print &#39;skip check !&#39;
                return func(*args,**kwargs)
            if len(args) != 2:
                raise Exception(&#39;Your args number is not two&#39;)
            else:
                return func(*args, **kwargs)
        return check_num
    return wraps
 
@check(&#39;false&#39;)
def add(x, y):
    return x + y

  对比上面通过decorator模块装饰函数的例子,我们可以发现,用decorator装饰函数的代码更加简洁易懂,但是他们二者的执行效率谁更高呢?下面我们通过Timer来测试下:

from timeit import Timer
 
print Timer(&#39;add(1,2)&#39;,setup=&#39;from __main__ import add&#39;).timeit(100000)
 
#将该段代码 加在 之前的例子中
#这里打印的是运行100000次的时间

   

  functools.wraps装饰执行结果:

2.37299322602

   

  decorator模块装饰执行结果:

3.42141566059

   

  执行效率wraps略高,但是这里是执行了10万次他们之间的差距约为1秒,所以我个人还是比较青睐于用decorator模块装饰函数,毕竟看起来易懂,写法也较为简单!本文就将装饰器介绍到这里了,当然也没有说尽装饰器的妙用,比如:装饰类...其原理是用类来当做装饰器,类里面需要用到__call__方法,至于装饰类的用法感兴趣的朋友自行百度咯!

 


Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn