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. I hope 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.
def use_logging(func): def wrapper(): logging.warn("%s is running" % func.__name__) return func() # 把 foo 当做参数传递进来时,执行func()就相当于执行foo() return wrapper def foo(): print('i am foo') foo = use_logging(foo) # 因为装饰器 use_logging(foo) 返回的时函数对象 wrapper,这条语句相当于 foo = wrapper foo() # 执行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.
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_logging def 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.
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
In this way, the parameters defined by the foo function can be defined in the wrapper function. At this time, someone wants to ask again, what if the foo function receives two parameters? What about the three parameters? What's more, I may pass many of them. When the decorator doesn't know how many parameters foo has, we can use *args instead:
def wrapper(*args): logging.warn("%s is running" % func.__name__) return func(*args) return wrapper
In this way, no matter how many parameters foo defines, I can pass them completely to func. go. This will not affect foo's business logic. At this time, some readers may ask, what if the foo function also defines some keyword parameters? For example:
def foo(name, age=None, height=None): print("I am %s, age %s, height %s" % (name, age, height))
At this time, you can specify the keyword function for the wrapper function:
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') @Foo def bar(): print ('bar') bar()
使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的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 # 函数 @logged def f(x): """does some math""" return x + x * x logged(f)
不难发现,函数 f 被with_logging取代了,当然它的docstring,__name__就是变成了with_logging函数的信息了。好在我们有functools.wraps,wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器里面的 func 函数中,这使得装饰器里面的 func 函数也有和原函数 foo 一样的元信息了。
from functools import wraps def 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 @logged def f(x): """does some math""" return x + x * x
一个函数还可以同时定义多个装饰器,比如:
@a @b @c def f (): pass
它的执行顺序是从里到外,最先调用最里层的装饰器,最后调用最外层的装饰器,它等效于
f = a(b(c(f)
The above is the detailed content of A detailed introduction to must-see Python decorators. For more information, please follow other related articles on the PHP Chinese website!