装饰器(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()
对上个例子进行修改:- 将辅助功能(记录时间)抽离成一个辅助函数,在辅助函数中调用主要功能函数- 优点:解耦,函数职责分离- 缺点:要通过辅助函数来调用主要功能函数,不方便- **我们的目标:能不能在调用主要功能函数时自动完成对时间的统计?**```pythonimport timedef 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
对上述修改简化调用```pythonimport timedef 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_wrapperdef 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 timedef 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_funcdef count_odds(lim=100):cnt = 0for i in range(lim):if i % 2 == 1:cnt +=1return cntif __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 timedef 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_funcdef count_odds(lim=100):cnt = 0for i in range(lim):if i % 2 == 1:cnt +=1return cntif __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 timedef 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_funcdef 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_wrapperdef count_odds(lim=100):cnt = 0for i in range(lim):if i % 2 == 1:cnt +=1return cntif __name__ == "__main__":count_odds(lim=10000)

参考自: https://zhuanlan.zhihu.com/p/26724125 廖雪峰关于装饰器的讲解 【可能是b站上最好的Python装饰器教程-哔哩哔哩】 https://b23.tv/PWFur9B 【Python小技巧:装饰器(Decorator)-哔哩哔哩】 https://b23.tv/9cxdjXC
