装饰器(decorator)的本质:函数闭包(function closure)的语法糖(Syntactic sugar)
1. 什么是函数闭包(function closure)
- 函数式语言(函数是一等公民,可作为变量使用)中的术语
- 函数闭包:一个函数,其参数和返回值都是函数
- 用于增强函数功能
- 面向切面编程(AOP)
先看一个例子:
- 函数逻辑(查找奇数)和辅助功能(记录时间)耦合在一起了
- 缺点:不方便修改,容易引起bug
- 能不能将辅助功能从主要功能函数中抽离出来? ```python import time
def print_odds(): “”” 输出0-100之间所有奇数,并统计函数执行时间 “”” start_time = time.time() # 起始时间
# 查找并输出所有奇数
for i in range(100):
if i % 2 ==1:
print(i)
end_time = time.time() # 结束时间
print("it takes {} s to find all the odds".format(end_time-start_time))
if name == “main“: print_odds()
对上个例子进行修改:
- 将辅助功能(记录时间)抽离成一个辅助函数,在辅助函数中调用主要功能函数
- 优点:解耦,函数职责分离
- 缺点:要通过辅助函数来调用主要功能函数,不方便
- **我们的目标:能不能在调用主要功能函数时自动完成对时间的统计?**
```python
import time
def count_time(func):
"""
统计某个函数的运行时间
"""
start_time = time.time() # 起始时间
func() # 执行函数
end_time = time.time() # 结束时间
print("it takes {} s to find all the odds".format(end_time-start_time))
def print_odds():
"""
输出0-100之间所有奇数,并统计函数执行时间
"""
# 查找并输出所有奇数
for i in range(100):
if i % 2 ==1:
print(i)
if __name__ == "__main__":
# print_odds()
count_time(print_odds)
对上述修改再修改:使用闭包增强
- 通过闭包增强主要功能函数print_odds,给它增加一个统计时间功能
- 缺点:需要显式进行闭包增强 ```python import time
def print_odds(): “”” 输出0-100之间所有奇数,并统计函数执行时间 “””
# 查找并输出所有奇数
for i in range(100):
if i % 2 ==1:
print(i)
def count_time_wrapper(func): “”” 闭包,用于增强函数func,给函数func增加统计时间的功能 “”” def improved_func(): start_time = time.time() # 起始时间 func() # 执行函数 end_time = time.time() # 结束时间 print(“it takes {} s to find all the odds”.format(end_time-start_time))
return improved_func
if name == “main“:
# 调用count_time_wrapper增强函数
print_odds = count_time_wrapper(print_odds)
print_odds()
print_odds() # 调用的还是第一次增强的print_odds
对上述修改简化调用
```python
import time
def count_time_wrapper(func):
"""
闭包,用于增强函数func,给函数func增加统计时间的功能
"""
def improved_func():
start_time = time.time() # 起始时间
func() # 执行函数
end_time = time.time() # 结束时间
print("it takes {} s to find all the odds".format(end_time-start_time))
return improved_func
@count_time_wrapper
def print_odds():
"""
输出0-100之间所有奇数,并统计函数执行时间
"""
# 查找并输出所有奇数
for i in range(100):
if i % 2 ==1:
print(i)
if __name__ == "__main__":
# 调用count_time_wrapper增强函数
# print_odds = count_time_wrapper(print_odds)
print_odds()
2. 什么是语法糖(Syntactic sugar)
- 指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用
- 语法糖没有增加新功能,只是一种更方便的写法
- 语法糖可以完全等价地转换为原本非语法糖的代码
- 装饰器在第一次调用被装饰函数时进行增强
- 下面两种方法完全等价
- print_odds = count_time_wrapper(print_odds) print_odds()
- @count_time_wrapper print_odds()
- 装饰器
@闭包函数名
- 装饰器在第一次调用被装饰函数时进行增强
- 增强时机?在第一次调用之前
- 增强次数?只增强一次,参考第二次修改执行代码
3. 主要功能函数有参数的装饰器
上述例子的主要功能函数print_odds没有参数
现在考虑有参数的装饰器
import time
def count_time_wrapper(func):
"""
闭包,用于增强函数func,给函数func增加统计时间的功能
"""
def improved_func():
start_time = time.time() # 起始时间
func() # 执行函数
end_time = time.time() # 结束时间
print("it takes {} s to find all the odds".format(end_time-start_time))
return improved_func
def count_odds(lim=100):
cnt = 0
for i in range(lim):
if i % 2 == 1:
cnt +=1
return cnt
if __name__ == "__main__":
print("增强前")
print(count_odds(lim=10000)) # 装饰前函数能正常返回,能接受参数
print("-"*20)
print("增强后")
count_odds = count_time_wrapper(count_odds)
print(count_odds())
# 返回:None
# 问题1:对于含有返回值的函数,调用闭包增强后,不能成功返回,但成功增强了辅助功能
print(count_odds(lim=10000)) # 装饰后函数不能正常返回,不能接收参数
# 直接报错
# 问题2:对于含有参数的函数,调用闭包增强后,不能成功接收参数
为了解决上述两个问题,对装饰器函数进行修改:
import time
def count_time_wrapper(func):
"""
闭包,用于增强函数func,给函数func增加统计时间的功能
"""
def improved_func(*args, **kwargs): # 增强函数应该把接收到的所有参数传给原函数
start_time = time.time() # 起始时间
ret = func(*args, **kwargs) # 执行函数
# 解决问题2:不能接收参数
end_time = time.time() # 结束时间
print("it takes {} s to find all the odds".format(end_time-start_time))
return ret # 解决问题1:不能正常返回值
return improved_func
def count_odds(lim=100):
cnt = 0
for i in range(lim):
if i % 2 == 1:
cnt +=1
return cnt
if __name__ == "__main__":
print("增强前")
print(count_odds(lim=10000)) # 装饰前函数能正常返回,能接受参数
print("-"*20)
print("增强后")
count_odds = count_time_wrapper(count_odds)
print(count_odds())
# 返回:None
# 问题1:对于含有返回值的函数,调用闭包增强后,不能成功返回,但成功增强了辅助功能
print(count_odds(lim=10000)) # 装饰后函数不能正常返回,不能接收参数
# 直接报错
# 问题2:对于含有参数的函数,调用闭包增强后,不能成功接收参数
3. 面试题:多个装饰器的执行顺序
import time
def count_time_wrapper(func):
"""
闭包,用于增强函数func,给函数func增加统计时间的功能
"""
def improved_func(*args, **kwargs): # 增强函数应该把接收到的所有参数传给原函数
start_time = time.time() # 起始时间
ret = func(*args, **kwargs) # 执行函数
# 解决问题2:不能接收参数
end_time = time.time() # 结束时间
print("it takes {} s to find all the odds".format(end_time-start_time))
return ret # 解决问题1:不能正常返回值
return improved_func
def log_wrapper(func):
"""
闭包,用于增强函数func:给func增加日志功能
"""
def improved_func(*args, **kwargs):
start_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) # 起始时间
ret = func(*args, **kwargs) # 执行函数
end_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) # 结束时间
print("Logging: func:{} runs from {} to {}".format(func.__name__, start_time, end_time))
return improved_func
@count_time_wrapper
@log_wrapper
def count_odds(lim=100):
cnt = 0
for i in range(lim):
if i % 2 == 1:
cnt +=1
return cnt
if __name__ == "__main__":
count_odds(lim=10000)
参考自: https://zhuanlan.zhihu.com/p/26724125 廖雪峰关于装饰器的讲解 【可能是b站上最好的Python装饰器教程-哔哩哔哩】 https://b23.tv/PWFur9B 【Python小技巧:装饰器(Decorator)-哔哩哔哩】 https://b23.tv/9cxdjXC