Python中的装饰器是一个用于修改类或者函数功能的可调用对象(callable),函数或者实现了__call__
方法的类都可以看作是可调用对象。Python中装饰器分为两大类:
Python中最简单的装饰器是一个嵌套函数。举例,使用装饰器函数elapsed
来统计函数执行耗时:
# _*_ coding=utf8 _*_
import time
import logging
logging.basicConfig(level=logging.INFO)
def elapsed(func):
"""统计函数执行耗时"""
def wrapper():
start_time = time.time()
func()
end_time = time.time()
logging.info(f'exec {func.__name__} elapsed:{end_time-start_time}')
return wrapper
def delay():
logging.info('delay...')
time.sleep(0.2)
# 装饰器就是普通函数,可以像普通函数那样直接调用
elapsed(delay)()
输出如下:
INFO:root:delay...
INFO:root:exec delay elapsed:0.20386290550231934
对于装饰器Python在语言层面给予了支持,对上面代码做如下修改:
@elapsed
def delay():
logging.info('delay...')
time.sleep(0.2)
delay()
输出入下:
INFO:root:delay...
INFO:root:exec delay elapsed:0.21143507957458496
使用装饰器后,打印函数信息,无法输出原函数信息,执行print(delay)
,输出<function elapsed.<locals>.wrapper at 0x0000018E8AFA4E50>
。
这里可以使用functools
模块来解决:
import functools
def elapsed(func):
"""统计函数执行耗时"""
@functools.wraps(func)
def wrapper():
start_time = time.time()
func()
end_time = time.time()
logging.info(f'exec {func.__name__} elapsed:{end_time-start_time}')
return wrapper
再次执行print(delay)
,输出<function delay at 0x00000186651B4E50>
。
装饰器可以被多次使用:
@elapsed
@elapsed
def delay():
logging.info('delay...')
time.sleep(0.2)
delay()
输出如下:
INFO:root:delay...
INFO:root:exec delay elapsed:0.21126818656921387
INFO:root:exec wrapper elapsed:0.21126818656921387
上述代码等价于如下调用方式:
def delay():
logging.info('delay...')
time.sleep(0.2)
# 装饰器就是普通函数,可以像普通函数那样直接调用
# 这里多次调用装饰器函数
elapsed(elapsed(delay))()
调用堆栈如下:
"""
logging.info() # wrapper2
logging.info() # wrapper1
logging.info() # delay
delay()
wrapper1() # func=delay
wrapper2() # func=wrapper1
elapsed(wrapper1) # 返回新的wrapper,其中func指向wrapper1,这里记录为wrapper2
elapsed(delay) # 返回wrapper,其中func指向delay函数,这里记录为wrapper1
"""
上节中提到的较为简单的装饰器是一个嵌套函数,带有参数的装饰器也是嵌套函数,只不过多嵌套一层:
# _*_ coding=utf8 _*_
import logging
import functools
logging.basicConfig(level=logging.DEBUG)
def exec(count):
def exec_wrapper(func):
@functools.wraps(func)
def func_wrapper():
for i in range(count):
logging.info(f'exec {func}')
func()
return func_wrapper
return exec_wrapper
@exec(3)
def func():
pass
func()
输出如下:
INFO:root:exec <function func at 0x000001A783724E50>
INFO:root:exec <function func at 0x000001A783724E50>
INFO:root:exec <function func at 0x000001A783724E50>
上述示例中的无法直接给函数func
传递参数,否则会抛异常:TypeError: func() takes 0 positional arguments but 1 was given
,因为加上装饰器后,再调用func
相当于调用func_wrapper
函数。想要给原函数传递参数,需对装饰器做如下改造:
def exec(count):
def exec_wrapper(func):
@functools.wraps(func)
def func_wrapper(*args, **kwargs):
for i in range(count):
logging.info(f'exec {func}')
func(*args, **kwargs)
return func_wrapper
return exec_wrapper
在func_wrapper
函数中添加*args
、**kwargs
两个参数,也可根据需要来添加其中某一个。
类装饰器与函数装饰器类似,只是类装饰器中要实现__call__
方法:
class Elapsed:
def __init__(self, func):
self.__func = func
def __call__(self, *args, **kwargs):
start_time = time.time()
self.__func(*args, **kwargs)
end_time = time.time()
logging.info(
f'exec {self.__func.__name__} elapsed:{end_time-start_time}')
@Elapsed
def func(secs=0.1):
time.sleep(secs)
装饰器不仅可以用于函数上,也可以用在类上,这里以类装饰器为例:
# _*_ coding=utf8 _*_
import logging
import time
logging.basicConfig(level=logging.DEBUG)
class LogClassName:
def __init__(self, cls):
self.__cls = cls
def __call__(self, *args, **kwargs):
logging.info(f'current class name: {self.__cls.__name__}')
return self.__cls(*args, **kwargs)
def __str__(self):
return f'{self.__cls}'
@LogClassName
class Info:
pass
logging.info(Info)
Info()
Python中的装饰器和装饰器模式有着相同的目的:在不修改原有功能代码的基础上对其做扩展。
Python在语言层面对与装饰器给与了支持,相对比较简洁,经典的装饰器模式在编码实现上通常比Python装饰器有更多的代码量。Python装饰器要明确的作用域某个函数或类上,装饰器模式则是针对某种类型的方法做扩展,具体扩展的对象在运行时才确定。此外,装饰器模式可以作为面向对象中继承的替代。
二者有相同的目的,但实现方式不同,Python装饰器可以看作是静态扩展,装饰器模式是动态扩展。