劝君惜取少年时
装饰器是修改其他函数的功能的函数(当然装饰器也可以装饰类)。它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,他们有助于让我们的代码更简短,也更Pythonic(Python范儿)
装饰器经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限验证等场景,装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
装饰器是一个函数,函数作为他的形参(装饰类的时候,类就是它的传参),返回值也是一个函数。
装饰器是高阶函数,但装饰器是对传入函数的功能的装饰(功能增强), 避免功能增强时侵入传入函数
实际上,所谓函数装饰器,就是通过装饰器函数,在不修改原函数的前提下,来对函数的功能进行合理的扩充。
被装饰函数 = 装饰器函数(被装饰函数)
可以用@functionname的方式,简化调用
def logger(fn): # 装饰器函数的传参是被装饰函数
def _logger(*args, **kwargs): # 装饰器内部嵌套函数的参数使用两个可变长参数,可以接受被装饰函数的任意数量和类型的参数
print('before')
ret = fn(*args, **kwargs) # 调用传入的被装饰函数,以此符合装饰器的初衷:“在不修改原函数的前提下”
print('after')
return ret
return _logger
@logger
def add1(x, y):
return x + y
# 等价于 add1 = logger(add1)
print(add1(4, 1000))
被装饰后的函数实质上已经是装饰器内部嵌套函数了,所以我们再打印这个函数的名字和文档说明,实际上是打印另外一个函数的名字和说明。如果仍想保有原函数的名称和文档说明,可以在装饰器内部嵌套函数内添加@wraps装饰器
from functools import wraps
def decorator_name(func):
@wraps(func) # 使用 functools.wraps 可以让打印出来的name还是原来的函数
def decorated(*args, **kwargs):
"""这是装饰器内部嵌套函数"""
print('before')
func(*args, **kwargs)
print('after')
return decorated
@decorator_name # 等价于:foo = decorator_name(foo)
def foo():
"""这是被装饰函数的文档说明"""
return "Function is running"
print(foo.__name__) # foo
print(foo.__doc__) # 这是被装饰函数的文档说明
foo()
from functools import wraps
def addition(func):
@wraps(func)
def with_log(*args, **kwargs):
with open('out.log', mode='a') as log:
log.write('>>>' + str(func.__name__) + '函数被调用\n')
return func(*args, **kwargs)
return with_log
@addition
def add(x, y):
print(x + y)
add(4, 5) # 9
在普通不带参的装饰器外层再套一层函数然后传入参数即可。
相当于给装饰器传入一个参数,把某些数值参数化,后再把这个装饰器返回,用这个传入过参数的装饰器 再去装饰 被装饰函数。
所以如果你想写带参装饰器,先写不带参的装饰器,再去包装一层函数, 对需要参数化的数值参数化即可。
继续以上面充当日志的装饰器为例,如果想把日志文件参数化,就可以使用带参装饰器
from functools import wraps
def decorator_have_args(log_file='out.log'):
def addition(func):
@wraps(func)
def with_log(*args, **kwargs):
with open(log_file, mode='a') as log:
log.write('>>>' + str(func.__name__) + '函数被调用\n')
return func(*args, **kwargs)
return with_log
return addition
"""
addition就是原来的装饰器,外面再套一层函数,加入想设置的参数,然后再把参数化的装饰器addition返回即可。
这样就可以指定日志输出的文件了
"""
@decorator_have_args('logg.log') # 等价于 add = decorator_have_args('logg.log')(add)
def add(x, y):
print(x + y)
add(4, 5)
Python 也支持多个装饰器,比如:
@funA
@funB
@funC
def fun():
pass
上面程序的执行顺序是里到外,所以它等效于下面这行代码:
fun = funA(funB(funC(fun)))
实现一个可以重运行函数的带参装饰器,可以用来装饰任何一个功能函数,只要被装饰的函数执行抛出错误,则等待hung_time秒重新执行该函数,同一个函数最多重运行N次,hung_time和N由装饰器参数决定。
import time
from functools import wraps
def retry_args_decorator(repetition: int, hung_time: int):
if type(repetition) != int or type(hung_time) != int:
raise TypeError('装饰器参数类型错误!')
def retry_decorator(func):
NUM = {'number': 0}
@wraps(func)
def wrap(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
if NUM['number'] < repetition:
NUM['number'] += 1
print('发生错误!,原因:{} {}秒后,函数开始重试运行第{}次'.format(e, hung_time, NUM['number']))
# 设置等待时间
time.sleep(hung_time)
# 递归调用装饰器
wrap(*args, **kwargs)
else:
raise Exception('函数已重复运行{}次,不可再运行!'.format(repetition))
return wrap
return retry_decorator
运行:
"""
函数test模拟3次抛出异常,第四次正常执行
"""
FLAG = 0
@retry_args_decorator(3, 1)
def test():
global FLAG
if FLAG < 3:
FLAG += 1
raise TypeError('类型错误!')
else:
print('success')
test()
结果:
发生错误!,原因:类型错误! 1秒后,函数开始重试运行第1次
发生错误!,原因:类型错误! 1秒后,函数开始重试运行第2次
发生错误!,原因:类型错误! 1秒后,函数开始重试运行第3次
success
# 和函数装饰器相同,只不过传入的是类
def single_wrapper_1(cls):
instance = None
# 内嵌包装函数,也和函数装饰器相同
def _instance(*args, **kwargs):
nonlocal instance # 由于instance是在上层局部作用域定义的,只能被访问,所以如果想修改它,必须使用nonlocal声明其为上级局部作用域中的instance
# 如果instance不存在,也就是没有实例存在,调用cls进行实例化赋值给instance,否则就直接返回instance保存的实例,从而实现单例
if not instance:
# 调用原来的类实例化
instance = cls(*args, **kwargs)
return instance
return _instance
@single_wrapper_1
class Test1:
def __init__(self):
self.num = 0
def add(self):
self.num = 99
ts1 = Test1()
ts2 = Test1()
print(ts1 is ts2) # True
# 当然,如果你不想使用nonlocal,就可以创建字典来存放,因为字典是可变数据类型,修改字典内部的元素,并没有修改字典本身,所以在局部作用域修改字典内部元素是被允许的,并且在局部作用域可以访问字典
def single_wrapper_2(cls):
instance = {}
def _instance(*args, **kwargs):
# 和上面方法判断逻辑一样,如果instance不存在,调用cls进行实例化,否则就直接返回instance
if not instance.get(cls):
instance[cls] = cls(*args, **kwargs)
return instance
return _instance
@single_wrapper_2
class Test2:
def __init__(self):
self.num = 0
def add(self):
self.num = 99
ts1 = Test2()
ts2 = Test2()
print(ts1 is ts2) # True