Home  >  Article  >  Backend Development  >  To understand Python decorators, it is enough to read this article

To understand Python decorators, it is enough to read this article

高洛峰
高洛峰Original
2017-02-17 11:07:321605browse

Before talking about Python decorators, I would like to give an example first. Although it is a bit dirty, it is very relevant to the topic of decorators.

The main function of the underwear that everyone has is to cover our shame, but in winter it cannot protect us from wind and cold. What should we do? One way we thought of was to transform the underwear to make it thicker and longer. In this way, it not only has the function of covering shame, but also provides warmth. However, there is a problem. After we transformed the underwear into trousers, , although it still has a shame-covering function, it is essentially no longer a real pair of underwear. So smart people invented trousers and put the trousers directly over the underwear without affecting the underwear. In this way, the underwear is still underwear. With the trousers, the baby will no longer be cold. Decorators are like the trousers we are talking about here. They provide warmth to our bodies without affecting the function of underwear.

Before talking about decorators, you need to understand one thing first. Functions in Python are different from Java and C++. Functions in Python can be passed to another function as parameters like ordinary variables, for example :

def foo():
    print("foo")def bar(func):
    func()bar(foo)

Officially back to our topic. A decorator is essentially a Python function or class that allows other functions or classes to add additional functionality without making any code modifications. The return value of the decorator is also a function/class object. It is often used in scenarios with cross-cutting requirements, such as: log insertion, performance testing, transaction processing, caching, permission verification, etc. Decorators are an excellent design to solve such problems. With decorators, we can extract a large amount of similar code that has nothing to do with the function itself into decorators and continue to reuse it. In a nutshell, the purpose of a decorator is to add additional functionality to an existing object.

Let’s look at a simple example first, although the actual code may be much more complicated than this:

def foo():
    print('i am foo')

Now there is a new requirement, hoping to record the execution log of the function, so I add it to the code Log code:

def foo():
    print('i am foo')
    logging.info("foo is running")

If the functions bar() and bar2() have similar requirements, what should be done? Write another logging in the bar function? This results in a lot of similar code. In order to reduce repeated code writing, we can do this and redefine a new function: specifically to process logs, and then execute the real business code after the logs are processed

def use_logging(func):
    logging.warn("%s is running" % func.__name__)
    func()def foo():
    print('i am foo')use_logging(foo)

Do this logic There is no problem, the function is implemented, but when we call it, we no longer call the real business logic foo function, but replace it with the use_logging function, which destroys the original code structure. Now we have to call it every time Every time I have to pass the original foo function as a parameter to the use_logging function, is there a better way? Of course there is, the answer is decorators.

Simple Decorator

def use_logging(func):

    def wrapper():
        logging.warn("%s is running" % func.__name__)
        return func()   # 把 foo 当做参数传递进来时,执行func()就相当于执行foo()
    return wrapperdef foo():
    print('i am foo')foo = use_logging(foo)  # 因为装饰器 use_logging(foo) 返回的时函数对象 wrapper,这条语句相当于  foo = wrapperfoo()                   # 执行foo()就相当于执行 wrapper()

use_logging is a decorator, it is an ordinary function, it wraps the function func that executes the real business logic in it, it looks like foo is decorated with use_logging , use_logging also returns a function, and the name of this function is wrapper. In this example, when the function enters and exits, it is called a cross-section. This programming method is called aspect-oriented programming.

@ Syntactic sugar

If you have been in contact with Python for a while, you must be familiar with the @ symbol. Yes, the @ symbol is the syntactic sugar of the decorator. It is placed in the function The place where the definition begins, so that the last step of reassignment can be omitted.

def use_logging(func):

    def wrapper():
        logging.warn("%s is running" % func.__name__)
        return func()
    return wrapper@use_loggingdef foo():
    print("i am foo")foo()

As shown above, with @ , we can omit the sentence foo = use_logging(foo), and directly call foo() to get the desired result. Did you see that the foo() function does not need to be modified in any way, just add a decorator where it is defined, and the call is still the same as before. If we have other similar functions, we can continue to call the decorator to decorate it. function without having to repeatedly modify the function or add new packages. In this way, we improve the reusability of the program and increase the readability of the program.

The reason why decorators are so convenient to use in Python is that Python functions can be passed as parameters to other functions like ordinary objects, can be assigned to other variables, can be used as return values, and can be defined. within another function.

*args, **kwargs

Someone may ask, what if my business logic function foo requires parameters? For example:

def foo(name):
    print("i am %s" % name)

We can specify the parameters when defining the wrapper function:

def wrapper(name):
        logging.warn("%s is running" % func.__name__)
        return func(name)
    return wrapper

这样 foo 函数定义的参数就可以定义在 wrapper 函数中。这时,又有人要问了,如果 foo 函数接收两个参数呢?三个参数呢?更有甚者,我可能传很多个。当装饰器不知道 foo 到底有多少个参数时,我们可以用 *args 来代替:

def wrapper(*args):
        logging.warn("%s is running" % func.__name__)
        return func(*args)
    return wrapper

如此一来,甭管 foo 定义了多少个参数,我都可以完整地传递到 func 中去。这样就不影响 foo 的业务逻辑了。这时还有读者会问,如果 foo 函数还定义了一些关键字参数呢?比如:

def foo(name, age=None, height=None):
    print("I am %s, age %s, height %s" % (name, age, height))

这时,你就可以把 wrapper 函数指定关键字函数:

def wrapper(*args, **kwargs):
        # args是一个数组,kwargs一个字典
        logging.warn("%s is running" % func.__name__)
        return func(*args, **kwargs)
    return wrapper

带参数的装饰器

装饰器还有更大的灵活性,例如带参数的装饰器,在上面的装饰器调用中,该装饰器接收唯一的参数就是执行业务的函数 foo 。装饰器的语法允许我们在调用时,提供其它参数,比如@decorator(a)。这样,就为装饰器的编写和使用提供了更大的灵活性。比如,我们可以在装饰器中指定日志的等级,因为不同业务函数可能需要的日志级别是不一样的。

def use_logging(level):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if level == "warn":
                logging.warn("%s is running" % func.__name__)
            elif level == "info":
                logging.info("%s is running" % func.__name__)
            return func(*args)
        return wrapper

    return decorator@use_logging(level="warn")def foo(name='foo'):
    print("i am %s" % name)foo()

上面的 use_logging 是允许带参数的装饰器。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器。我们可以将它理解为一个含有参数的闭包。当我 们使用@use_logging(level="warn")调用的时候,Python 能够发现这一层的封装,并把参数传递到装饰器的环境中。

@use_logging(level="warn")等价于@decorator

类装饰器

没错,装饰器不仅可以是函数,还可以是类,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器主要依靠类的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。

class Foo(object):
    def __init__(self, func):
        self._func = func

    def __call__(self):
        print ('class decorator runing')
        self._func()
        print ('class decorator ending')@Foodef bar():
    print ('bar')bar()

functools.wraps

使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、__name__、参数列表,先看例子:

# 装饰器def logged(func):
    def with_logging(*args, **kwargs):
        print func.__name__      # 输出 'with_logging'
        print func.__doc__       # 输出 None
        return func(*args, **kwargs)
    return with_logging# 函数@loggeddef f(x):
   """does some math"""
   return x + x * xlogged(f)

不难发现,函数 f 被with_logging取代了,当然它的docstring,__name__就是变成了with_logging函数的信息了。好在我们有functools.wraps,wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器里面的 func 函数中,这使得装饰器里面的 func 函数也有和原函数 foo 一样的元信息了。

from functools import wrapsdef logged(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print func.__name__      # 输出 'f'
        print func.__doc__       # 输出 'does some math'
        return func(*args, **kwargs)
    return with_logging@loggeddef f(x):
   """does some math"""
   return x + x * x

装饰器顺序

一个函数还可以同时定义多个装饰器,比如:

@a
@b
@c
def f ():
    pass

它的执行顺序是从里到外,最先调用最里层的装饰器,最后调用最外层的装饰器,它等效于

f = a(b(c(f)))

zhijun liu首发于知乎专栏Python之禅,原文地址:https://zhuanlan.zhihu.com/p/24900548

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