主要是个人使用与理解,借鉴了网上其他大神的写法与思路
恶补了 Python 装饰器的八种写法,你随便问~
python装饰器带括号和参数
一、闭包
闭包相当于函数中,嵌套另一个函数,并返回
def outer_func(a): # 外层函数
def inner_func(b): # 内层函数
return a + b
return inner_func # 此处需要返回内层函数,才行行程闭包
func = outer_func(10) # 将10传给outer_func,返回inner_func,并赋值给func
print(func(5)) # 将5传给func(即inner_func),并打印
# 结果为15
二、装饰器
装饰器本质上是一个闭包函数,它使用闭包的特性来记住并调用它装饰的函数
闭包传入的是变量,而装饰器利用闭包的原理,可以传入函数(在装饰器中,也可以把函数当作变量)。
任何可调用的对象都可以作为装饰器,只要它接受一个函数作为参数并返回一个函数。这通常意味着装饰器可以是:
函数:这是最常见的装饰器形式,它接受一个函数作为参数并返回一个新的函数。
类:类也可以用作装饰器,如果它们实现了__call__方法。在这种情况下,类的实例将是一个可调用对象,它可以接受一个函数作为参数,并在__call__方法中返回一个新的函数。
带有__call__方法的任何对象:任何实现了__call__方法的对象都可以被用作装饰器。这意味着对象在被像函数那样调用时(即使用括号)会执行__call__方法。
2.1 使用函数作为装饰器
下面定义了一个logger_printer,用于在函数执行前后在控制台打印输出,可以需要的函数前都加上装饰器。*args, **kwargs用于接收test_func的参数
def logger_printer(func):
def wrapper(*args, **kwargs):
print(f'【开始执行】:{func.__name__}方法!')
func(*args, **kwargs) # 此处执行被装饰的函数
print(f'【结束执行】:{func.__name__}方法!')
return wrapper
@logger_printer
def anohter_func():
print('回到anohter_func方法')
@logger_printer
def test_func():
print('回到test_func方法')
anohter_func()
test_func()
同理,我们也可以做一个统计函数执行时间的装饰器
def time_calculator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
func(*args, **kwargs)
end_time = time.time()
expend_time = end_time - start_time
print(f'【耗时统计:】{func.__name__}方法耗时{expend_time}秒')
return wrapper
@time_calculator
def test():
print('回到test方法,开始等待5s')
time.sleep(5)
print('等待5s结束')
test()
2.2 传入参数的(函数)装饰器
上面2.1使用的装饰器没有传入参数,导致装饰器只能有固定的用法,当装饰器能够接收参数时,它的功能就非常灵活了。
制作一个延时功能的装饰器timer,再使用之前的time_calculator对其进行耗时统计
多个叠加的装饰器,其实就是上方的装饰器装饰了下方的装饰器,层层往下装饰,运行的顺序也是从上往下
def timer(delay_time: int):
def wrapper(func):
def inner(*args, **kwargs):
print(f'开始等待【{delay_time}】秒')
time.sleep(delay_time)
print(f'结束等待【{delay_time}】秒')
func(*args, **kwargs) # 此处执行被装饰的函数
return inner
return wrapper
@time_calculator
@timer(2)
def test():
print('回到test方法')
test()
这边可以看到“【耗时统计:】inner方法耗时2.002117872238159秒”,这边写的是inner方法,而不是我们想要装饰的test方法,或是退一步也应该写timer方法吧。
调试时可以发现time_calculator内传入的方法的确是inner。
通过在inner前增加wraps装饰器,即可以解决该问题。
from functools import wraps
def timer(delay_time: int):
def wrapper(func):
@wraps(func)
def inner(*args, **kwargs):
print(f'开始等待【{delay_time}】秒')
time.sleep(delay_time)
print(f'结束等待【{delay_time}】秒')
func(*args, **kwargs) # 此处执行被装饰的函数
return inner
return wrapper
如果涉及多个装饰器叠加,并要得到正确的函数名,需要在多个装饰内,增加wraps
如何理解wraps,见开头分享的文章,里面有wraps的介绍。
2.3 使用类作为装饰器
类作为装饰器,其用法实际和函数作为装饰器是差不多,我们把之前logger装饰器使用类再写一遍。
class Logger:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print(f'【开始执行】:{self.func.__name__}方法!')
self.func(*args, **kwargs) # 此处执行被装饰的函数
print(f'【结束执行】:{self.func.__name__}方法!')
@Logger
def test():
print('回到test方法了')
test()
如果使用函数装饰器,与类装饰器叠加使用可能会有问题,如下:当类装饰器放在函数装饰器上时,会由于Logger类没有__name__属性引起AttributeError
class Logger:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print(f'【开始执行】:{self.func.__name__}方法!')
result = self.func(*args, **kwargs) # 执行被装饰的函数并保存返回值
print(f'【结束执行】:{self.func.__name__}方法!')
return result # 返回被装饰函数的返回值
def time_calculator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs) # 执行被装饰的函数并保存返回值
end_time = time.time()
expend_time = end_time - start_time
print(f'【耗时统计:】{func.__name__}方法耗时{expend_time}秒')
return result # 返回被装饰函数的返回值
return wrapper
@time_calculator
@Logger
def test():
print('回到test方法了')
time.sleep(3)
test()
两种解决方法:1、将Logger与time_calculator上下交换下位置,不过内部的逻辑可能需要修改;2、在Logger内加入__getattr__方法用于返回不存在的对象属性。
class Logger:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print(f'【开始执行】:{self.func.__name__}方法!')
result = self.func(*args, **kwargs) # 执行被装饰的函数并保存返回值
print(f'【结束执行】:{self.func.__name__}方法!')
return result # 返回被装饰函数的返回值
def __getattr__(self, item):
return getattr(self.func, item)
2.4 使用带参数的类作为装饰器
在2.3的Logger基础上再加点传入日志级别的数据
由于 Logger 类正确地在 wrapper 函数中调用了原始函数 func,并且没有修改或覆盖任何与函数名称相关的属性,因此它不会干扰到外部装饰器( time_calculator)对函数名称的访问,所以不会报错。
class LogLevel(Enum):
DEBUG = 1
INFO = 2
WARNING = 3
ERROR = 4
class Logger:
def __init__(self, level: LogLevel):
self.level = level
def __call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f'【{self.level.name}】:开始运行【{func.__name__}】')
func(*args, **kwargs)
print(f'【{self.level.name}】:结束运行【{func.__name__}】')
return wrapper
@time_calculator
@Logger(LogLevel.INFO)
def test():
time.sleep(3)
print('回到test方法了')
test()
2.5 可以装饰类的装饰器
常用的就是单例模式了singleton
def singleton(cls):
instance = {}
def get_instance(*args, **kwargs):
if cls not in instance:
instance[cls] = cls(*args, **kwargs)
return instance[cls]
return get_instance
@singleton
class Abc:
pass
a1 = Abc()
a2 = Abc()
print(id(a1), id(a2))
就这样吧,装饰器用得太复杂,反而背离了初衷