之前一直在用Python的装饰器,比如著名的5行代码
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello_world():
return "Hello, World!"
但没有对其实现方式过多探究,今天闲来无事分析下装饰器
装饰器顾名思义,就是对函数进行装饰的一个语法糖🍬,其基础是闭包,其作用就是在不给函数添加额外代码的情况下给函数添加功能
比如我们想打印当前时间:
import datetime
now = datetime.datetime.now()
def print_time(now):
print(now)
print_time(now)
假设我们希望给print_time函数添加打印年月日时分秒的功能,我们可以这样写:
import datetime
now = datetime.datetime.now()
def print_time(now):
print(now.year)
print(now.month)
print(now.day)
print(now.hour)
print(now.minute)
print(now.second)
print(now)
print_time(now)
这样写有两个问题:
- 破坏了print_time的定义
- 如果存在成百上千个类似print_time的函数,复制粘贴可能都要累死
为了解决上面的问题Python给了语法糖decrator,所以上面的代码可以改写成:
import datetime
now = datetime.datetime.now()
def split(now):
def decorator(func):
def wrapper(*args, **kw):
print(now.year)
print(now.month)
print(now.day)
print(now.hour)
print(now.minute)
print(now.second)
return func(*args, **kw)
return wrapper
return decorator
@split(now)
def print_time(now):
print(now)
print_time(now)
这里的wrapper函数和其所在的上下文构成了闭包
关于闭包可以参考:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures
https://www.zhihu.com/question/34210214
这里的@split(now)相当于执行了
print_time = split(now)(print_time)
前面的print_time为变量名,后面的print_time为函数名,也就是说此时print_time变量名指向了split(now)(print_time)的返回对象即wrapper函数(注意,split(now)返回了decorator函数,所以split(now)(print_time)即调用了decrator函数,返回wrapper函数)
我们可以打印一下此时的print_time的__name__属性
>>> print(print_time.__name__)
wrapper
但有的时候我们需要__name__属性为被装饰的函数名,而不是装饰器中函数的函数名,可能我们需要在wrapper函数中加上重命名的语句
wrapper.__name__ = func.__name__
但我们有更好的方式:
import functools
import datetime
now = datetime.datetime.now()
def split(now):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print(now.year)
print(now.month)
print(now.day)
print(now.hour)
print(now.minute)
print(now.second)
return func(*args, **kw)
return wrapper
return decorator
@split(now)
def print_time(now):
print(now)
print_time(now)
上面例子中由于装饰器中存在参数now,所以需要三层嵌套装饰器,如果没有参数,则只需要两层即可
注意wrapper中的*args, **kw,这种写法使得该函数能够接受任意形式参数的调用
一个函数可以被多个装饰器装饰
import functools
import datetime
now = datetime.datetime.now()
def year(now):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print(now.year)
return func(*args, **kw)
return wrapper
return decorator
def month(now):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print(now.month)
return func(*args, **kw)
return wrapper
return decorator
def day(now):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print(now.day)
return func(*args, **kw)
return wrapper
return decorator
def hour(now):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print(now.hour)
return func(*args, **kw)
return wrapper
return decorator
def minute(now):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print(now.minute)
return func(*args, **kw)
return wrapper
return decorator
def second(now):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print(now.second)
return func(*args, **kw)
return wrapper
return decorator
@year(now)
@month(now)
@day(now)
@hour(now)
@minute(now)
@second(now)
def print_time(now):
print(now)
print_time(now)
可以发现结果一样。但可能你会对上面装饰器的执行顺序有些迷惑,没关系,我们下面分析一下
首先,我们知道这几个装饰器就相当于
def print_time(now):
print(now)
print_time = year(now)(month(now)(day(now)(hour(now)(minute(now)(second(now)(print_time))))))
参考:https://docs.python.org/3/reference/compound_stmts.html#function
此时print_time指向的是最外层函数year中decorator返回的wrapper1函数;wrapper1的参数为month中decrator返回的wrapper2函数;wrapper2的参数为day中decrator返回的wrapper3函数;wrapper3的参数为hour中decrator返回的wrapper4函数;wrapper4的参数为minute中decrator返回的wrapper5函数;wrapper5的参数为second中decrator返回的wrapper6函数;而wrapper6的参数即为print_time函数
有没有感觉很像递归
所以以下代码的执行结果就是
import functools
import datetime
now = datetime.datetime.now()
def year(now):
def decorator(func):
print('decorator:', func.__name__)
def year(*args, **kw):
print('wrapper:', func.__name__)
return func(*args, **kw)
return year
return decorator
def month(now):
def decorator(func):
print('decorator:', func.__name__)
def month(*args, **kw):
print('wrapper:', func.__name__)
return func(*args, **kw)
return month
return decorator
def day(now):
def decorator(func):
print('decorator:', func.__name__)
def day(*args, **kw):
print('wrapper:', func.__name__)
return func(*args, **kw)
return day
return decorator
def hour(now):
def decorator(func):
print('decorator:', func.__name__)
def hour(*args, **kw):
print('wrapper:', func.__name__)
return func(*args, **kw)
return hour
return decorator
def minute(now):
def decorator(func):
print('decorator:', func.__name__)
def minute(*args, **kw):
print('wrapper:', func.__name__)
return func(*args, **kw)
return minute
return decorator
def second(now):
def decorator(func):
print('decorator:', func.__name__)
def second(*args, **kw):
print('wrapper:', func.__name__)
return func(*args, **kw)
return second
return decorator
@year(now)
@month(now)
@day(now)
@hour(now)
@minute(now)
@second(now)
def print_time(now):
print(now)
print_time(now)
decorator: print_time
decorator: second
decorator: minute
decorator: hour
decorator: day
decorator: month
wrapper: month
wrapper: day
wrapper: hour
wrapper: minute
wrapper: second
wrapper: print_time
2021-07-26 09:24:00.976288
把上面的装饰器改写为如下的嵌套函数的形式,便于理解
print_time = year(now)(month(now)(day(now)(hour(now)(minute(now)(second(now)(print_time))))))
print_time(now)
我们姑且称这两行语句分别为构建和调用,构建过程可以理解为简单的函数嵌套执行顺序,是从内而外执行;
由于每一层返回的都是其子层的函数,所以调用顺序可以简写为如下:
print_time(wrapper6(wrapper5(wrapper4(wrapper3(wrapper2(wrapper1(now)))))))
因而就有了上面的结果。
本文详细探讨了Python装饰器的工作原理,包括闭包的应用、如何避免代码重复,以及使用functools.wraps实现函数名保留。通过实例演示了装饰器的嵌套和执行顺序,帮助读者理解装饰器在函数增强中的作用。





