3 道练习题·预计 30 分钟·做对一题解锁下一段
上一章学了闭包,这一章我们一步步演变一个需求,来认识 Python 中非常有特色的语法——装饰器(Decorator)。
先看一个最简单的打卡函数:
def punch():
print('昵称:两点水 部门:研发部 上班打卡成功')
punch()输出:
昵称:两点水 部门:研发部 上班打卡成功
很朴素。
产品经理跑过来说:「不行啊,怎么没有日志?打卡前后都给我打印一下提示!」
直接改函数:
def punch():
print('[LOG] 开始执行')
print('昵称:两点水 部门:研发部 上班打卡成功')
print('[LOG] 执行结束')
punch()可以是可以——但这样改变了函数本身的功能结构。本来 punch 只是打卡,现在硬塞进了日志。
而且想象一下:如果还有 holiday()、apply_leave()、overtime() 等等很多函数都要加日志——你打算每个函数都写一遍 [LOG] 开始执行 吗?
代码重复 = 坏味道。
我们知道 Python 函数有两个特点:
那就把日志抽出来:
def punch():
print('昵称:两点水 部门:研发部 上班打卡成功')
def add_log(func):
print('[LOG] 开始执行')
func()
print('[LOG] 执行结束')
add_log(punch)输出:
[LOG] 开始执行
昵称:两点水 部门:研发部 上班打卡成功
[LOG] 执行结束
这下 punch 没动过,任何想加日志的函数都可以扔进 add_log。
但调用方式变了——以前是 punch(),现在变成 add_log(punch)。如果项目里 punch() 已经被调用了 100 处,难道全要改成 add_log(punch)?
我们想要的是:不改函数本身,也不改调用方式,就给函数加上日志。
这就是装饰器要解决的事。
装饰器的写法和闭包很像——只不过它接收的是一个函数:
def decorator(func):
def wrapper():
print('[LOG] 开始执行')
func()
print('[LOG] 执行结束')
return wrapper
def punch():
print('昵称:两点水 部门:研发部 上班打卡成功')
f = decorator(punch)
f()输出:
[LOG] 开始执行
昵称:两点水 部门:研发部 上班打卡成功
[LOG] 执行结束
装饰器函数一般做这三件事:
可是认真一看:这写法怎么看都比直接传函数还麻烦啊——还要 f = decorator(punch) 拿到包装后的函数再调用。
这就是为什么 Python 引入了 @ 语法糖——它让定义装饰器、把装饰器调用原函数再把结果赋值给原函数对象名的过程变得非常简洁。
用法:在原函数定义上方加 @装饰器名:
def decorator(func):
def wrapper():
print('[LOG] 开始执行')
func()
print('[LOG] 执行结束')
return wrapper
@decorator
def punch():
print('昵称:两点水 部门:研发部 上班打卡成功')
punch()输出:
[LOG] 开始执行
昵称:两点水 部门:研发部 上班打卡成功
[LOG] 执行结束
@decorator 等价于在 def punch() 之后偷偷帮你执行了一句:
punch = decorator(punch)所以从此以后,调用 punch() 实际上调的是 wrapper(),外层加了日志,里面再调原 punch。
Python 在引入装饰器时,没有引入任何新的语法特性——它都是基于函数是对象、函数可嵌套这些已有的能力。装饰器不是 Python 特有的概念,每种语言都可以有这种思想;只不过 Python 给了它
@这个糖,写起来格外漂亮。
一句话:装饰器 = 不修改原函数和调用方式,给函数额外加一层功能。
请写一个装饰器 log:
print('开始执行')print('执行结束')然后用 @log 装饰一个 hello() 函数(函数体里 print('hello world')),再调用 hello()。
输出应该是:
开始执行
hello world
执行结束