1. 装饰器的本质
装饰器的本质:函数闭包
的语法糖
2. 闭包
- 实现一个函数,主功能:输出0-100所有奇数;辅助功能:统计执行时间。
def print_odds():
"""
输出0~100之间所有奇数,并统计函数执行时间
"""
start_time = time.clock() # 起始时间
# 查找并输出所有奇数
for i in range(100):
if i % 2 == 1:
print(i)
end_time = time.clock() # 结束时间
print("it takes {} s to find all the olds".format(end_time - start_time))
if __name__ == '__main__':
print_odds()
存在问题:
- 函数逻辑(查找奇数)和辅助功能(记录时间)耦合在一起了.
- 缺点: 不方便修改,容易引起bug
优化思路:
能不能将辅助功能从主要功能函数中抽离出来?
- 将辅助功能抽离出来,形成一个函数。
def count_time(func):
"""
统计某个函数的运行时间
"""
start_time = time.clock() # 起始时间
func() # 执行函数
end_time = time.clock() # 结束时间
print("it takes {} s to find all the olds".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)
存在问题:
将辅助功能(记录时间)抽离成一个辅助函数count_time,在辅助函数count_time中调用主要功能函数print_odds.
- 优点: 解耦,函数职责分离.
- 缺点: 要通过辅助函数来调用主要功能函数,不方便.
优化思路:
我们的目标: 能不能在调用主要功能函数时自动完成对时间的统计?
- 使用闭包
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.clock() # 起始时间
func() # 执行函数
end_time = time.clock() # 结束时间
print("it takes {} s to find all the olds".format(end_time - start_time))
return improved_func
if __name__ == '__main__':
# 调用count_time_wrapper增强函数
print_odds = count_time_wrapper(print_odds)
print_odds()# improved
print_odds()# improved
print_odds()# improved
print_odds()# improved
print_odds()# improved
闭包的本质:
- 闭包本质上是一个函数
- 闭包函数的传入参数和返回值也都是函数
- 闭包函数的返回值函数是对传入函数进行增强后的结果
存在问题:
- 缺点: 在调用主函数之前,需要显式进行闭包增强。可读性还是差。
解决思路:
- 装饰器
"""
通过装饰器进行函数增强,只是一种语法糖,本质上跟上个程序完全一致.
"""
def count_time_wrapper(func):
"""
闭包,用于增强函数func: 给函数func增加统计时间的功能
"""
def improved_func():
start_time = time.clock() # 起始时间
func() # 执行函数
end_time = time.clock() # 结束时间
print(
"it takes {} s to find all the olds".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__':
# 装饰器等价于在第一次调用函数时执行以下语句:
# print_odds = count_time_wrapper(print_odds)
print_odds()
3. 语法糖
语法糖:指的是计算机语言中添加某种语法,这种语法对功能没有用但是对程序员更加方便。
4. 面试常考问题
- 若主函数有返回值,装饰器中如何接收。
def general_wrapper(func):
def improved_func(*args, **kwargs): # 接收函数参数
# 增强功能
ret = func(*args, **kwargs) # 传入参数并记录返回值
# 增强功能
return ret # 返回未增强函数的返回值
return improved_func
- 装饰器嵌套关系
def count_time_wrapper(func):
"""
闭包,用于增强函数func: 给函数func增加统计时间的功能
"""
def improved_func():
start_time = time.clock() # 起始时间
func() # 执行函数
end_time = time.clock() # 结束时间
print("it takes {} s to find all the olds".format(end_time - start_time))
return improved_func
def log_wrapper(func):
"""
闭包,用于增强函数func: 给func增加日志功能
"""
def improved_func():
start_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) # 起始时间
func() # 执行函数
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():
"""
输出0~100之间所有奇数,并统计函数执行时间
"""
cnt = 0
for i in range(100):
if i % 2 == 1:
cnt += 1
return cnt
if __name__ == '__main__':
count_odds()
先进count的improved_func(),而improved_func() = log_wrapper增强后的函数,所以进入log_wrapper的improved_func(),然后print,所以先输出log的print,后输出count的print。
如果想不明白就先写成闭包,一步步调用,debug看。
def wrapper1(func1):
print('set func1') # 在wrapper1装饰函数时输出
def improved_func1():
print('call func1') # 在wrapper1装饰过的函数被调用时输出
func1()
return improved_func1
def wrapper2(func2):
print('set func2') # 在wrapper2装饰函数时被输出
def improved_func2():
print('call func2') # 在wrapper2装饰过的函数被调用时输出
func2()
return improved_func2
# @wrapper1
# @wrapper2
def original_func():
pass
if __name__ == '__main__':
# original_func = wrapper1(wrapper2(original_func))
# print(original_func.__name__) # original_func = original_func()
original_func = wrapper2(original_func)
# print(original_func.__name__) # original_func = improved_func2(封装original_func)()
original_func = wrapper1(original_func)
# print(original_func.__name__) # original_func = improved_func1(封装improved_func2(封装original_func)())()
original_func()
print('-----')
original_func()
增强的时候不进入def函数。
开始根据优先级,先走wrapper2,输出set func2,然后走wrapper1,输出func1。
此时original_func()先走wrapper1的def,然后主函数是增强的函数improved_func2,然后在进入improved_func2曾倩的函数是original_func()最初的。所以先输出call func1 后 输出 call func2
- 如何实现在装饰器中传入参数
在封装一层就可以实现
def log_wrapper(info='Everything works'):
def internal_log_wrapper(func):
def improved_func():
start_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) # 起始时间
func() # 执行函数
end_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) # 结束时间
print("Logging: func:{} runs from {} to {}, info:[{}]".format(func.__name__, start_time, end_time, info))
return improved_func
return internal_log_wrapper
@log_wrapper(info='informmmmmmmm')
def count_odds():
"""
输出0~100之间所有奇数,并统计函数执行时间
"""
cnt = 0
for i in range(100):
if i % 2 == 1:
cnt += 1
return cnt
if __name__ == '__main__':
count_odds()