1. 定义函数时造成高耦合的问题
01_highCouplingExample.py
def print_odds():
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))
在定义print_odds
函数时,给函数赋予了两个功能
- 计时功能
- 输出奇数功能
若在项目中遇到更复杂的需求,此时耦合在一起,对代码的修改与维护不友好,所以需要将两个功能给独立开来
2. 要想明白如何将代码独立开来,首先需要理解在python语言中,所有的东西都是一个对象
02_funcDividedExample.py
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():
for i in range(100):
if i % 2 == 1:
print(i)
if __name__ == '__main__':
count_time(print_odds)
在这里,可以将计时功能赋予count_time
函数,而将输出奇数功能赋予'print_odds'函数,在主函数里,将print_odds
作为参数传入count_time
函数中
若在主函数中执行print(print_odds.__name__)
,可以得到输出结果为
print_odds
直白来说,就是函数名不加括号时,就表示一个名为自己的函数对象
3.既然函数可以作为对象传入,自然也可以作为对象被传出
03_closureFuncExample.py
def count_time_wrapper(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 print_odds():
for i in range(100):
if i % 2 == 1:
print(i)
if __name__ == '__main__':
print_odds_addedCountTime = count_time_wrapper(print_odds)
print_odds_addedCountTime()
在此例中,count_time_wrapper
是一个闭包
闭包本质上就是一个函数,通过传入一个函数和返回一个函数来对传入的函数进行增强
但是需要注意的是返回的不是增强的传入函数,而是返回一个传入函数增强的结果
举个例子
当执行print(print_odds_addedCountTime.__name__)
语句后,返回的是
improved_func
也就是说返回的是闭包函数里包含的增强函数,而不是传进去的待增强的函数
4.对上个程序稍加修改,就可以得到我们的第一个装饰器
04_decoratorExample.py
def count_time_wrapper(func):
"""
This is a function which can decorate func
:param func: a function need to be improved
:return: improved_func
"""
def improved_func():
"""
This is a function which can record how long func runs
:return: None
"""
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():
"""
This is a function which can print odds among 1-100
:return: None
"""
for i in range(100):
if i % 2 == 1:
print(i)
if __name__ == '__main__':
print_odds()
只需要在待装饰的函数上用@闭包函数名
,即可对待装饰函数进行增强
- 装饰器其实就是一个语法糖,本质上与闭包函数完全一样
- 需要注意的是,通过装饰器的写法,是将闭包函数内的增强函数作为对象返回,待增强的函数的对象其实已经变成了闭包函数内的增强函数
通过执行print(print_odds.__name__)
和print(print_odds.__doc__)
语句,结果是
improved_func
This is a function which can record how long func runs
:return: None
如果没有装饰,执行语句,返回的结果是
print_odds
This is a function which can print odds among 1-100
:return: None
5.那如何解决装饰器重写函数的名字和注释文档的问题呢
通过导入functiontools
类来解决
05_decoratorExample.py
from functools import wraps
# 节省篇幅,注释在此笔记中省略,省略注释同 04_decoratorExample.py
def count_time_wrapper(func):
@wraps(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():
for i in range(100):
if i % 2 == 1:
print(i)
@wraps
接受一个函数对象来进行装饰,并加入了复制函数名称、注释文档、参数列表等等的功能。这可以让我们在装饰器里面访问在装饰之前的函数的属性。
其实在improved_func()
函数中可以添加
improved_func.__name__ = func.__name__
improved_func.__doc__ == func.__doc__
等语句使得装饰后的函数属性与之前一致,但是,有现成的@wraps
不用干啥?写代码又不是按行数算工资。
6.若需要装饰带参数函数
06_decoratorExample.py
def count_time_wrapper(func):
@wraps(func)
def improved_func(*args, **kwargs):
start_time = time.time()
func(*args, **kwargs)
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(lim=10000):
for i in range(lim):
if i % 2 == 1:
print(i)
此时仔细讲一下@装饰器
的工作原理
只有第一次使用装饰器被装饰的函数时,该函数才会被增强
- 当被装饰的函数没有被用到时,则该函数不会被增强
-
@装饰器
只会生效一次
通过调试可以发现,使用@装饰器
增强函数的过程不会被显式出来,即
def count_time_wrapper(func):
def improved_func(*args, **kwargs):
start_time = time.time()
func(*args, **kwargs)
end_time = time.time()
print("It takes {} s to find all the odds".format(end_time - start_time))
return improved_func
print_odds = count_time_wrapper(print_odds)
与@count_time_wrapper
完全等价,且会一次执行到位
个人认为可以理解成这样:
编译器检测到马上要执行一个被装饰器装饰的函数了,那么就立刻对该函数进行加强,该函数加强完毕,或者说该函数被装饰好了,才会开始执行这个函数。
7.函数如何被多个装饰器装饰
对上面代码新增日志记录log_wrapper
装饰器
07_decoratorExample.py
def log_wrapper(func):
@wraps(func)
def improved_func(*args, **kwargs):
start_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) # 起始时间
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
@log_wrapper
@count_time_wrapper
def print_odds(lim=10000):
经过这样的装饰,可以理解为print_odds
函数先被count_time_wrapper
函数包裹,再被log_wrapper
函数包裹。从装饰器装饰的顺序来说,离被装饰函数越近,则该装饰器则先增强被装饰函数了,增强后的结果再被离的远的装饰器增强。
输出结果为
……
99
It takes 0.0 s to find all the odds
Logging: func:print_odds runs from 2022-02-22 19:17:19 to 2022-02-22 19:17:19
8.带有参数的装饰器
不难发现,我们之前自己写的装饰器都不带有参数,但是@wraps
装饰器带有参数,这是因为,当使用@装饰器
语法时,其实就是在应用一个以单个函数作为参数的一个包裹函数。由此我们理解为@带参数的装饰器(装饰器)
语法等同于@带参数的装饰器函数返回的装饰器
语法
对上一个代码中的log_wrapper
装饰器在做一个闭包函数包裹,该部分修改如下:
08_decoratorExample.py
def logit(logfile='out.log'):
print("我只执行了一次")
def log_wrapper(func):
@wraps(func)
def improved_func(*args, **kwargs):
start_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) # 起始时间
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))
print('在{}中写入日志'.format(logfile))
return improved_func
return log_wrapper
#装饰器部分修改如下
@logit('new_out.log')
@count_time_wrapper
def print_odds(lim=10000):
#主函数修改如下
if __name__ == '__main__':
print_odds(5)
print_odds(5)
结果为:
我只执行了一次
1
3
It takes 0.0 s to find all the odds
Logging: func:print_odds runs from 2022-02-22 20:25:10 to 2022-02-22 20:25:10
在new_out.log中写入日志
1
3
It takes 0.0 s to find all the odds
Logging: func:print_odds runs from 2022-02-22 20:25:10 to 2022-02-22 20:25:10
在new_out.log中写入日志
- 解释
上一节中@log_wrapper
相当于print_odds = log_wrapper(print_odds)
这一节中logit('new_out.log')
返回了一个和上一节中log_wrapper
一样的闭包函数,所以@logit('new_out.log')
其实相当于print_odds = logit('new_out.log')(print_odds)
- 注意
我在logit('new_out.log')
函数中执行了print("我只执行了一次")
,在主函数中,增强后的函数执行了两次,但只出现一次“我只执行了一次”
,确实说明了第六节中讲的"只有第一次使用装饰器被装饰的函数时,该函数才会被增强"