熟悉 Java 的程序员一定对 Java 中强大的注解有所了解,Python 在一定程度上受到了 Java 的影响,诞生了 Python 的装饰器特性。 Python 的装饰器是一个非常强大的功能,本文我们就来详细介绍一下 Python 的装饰器特性。 例如我们熟知的类静态方法在定义时就常常会使用 @staticmethod 与 @classmethod 装饰器:
class TestClass:
@staticmethod
def staticfoo():
pass
@classmethod
def clsfoo(cls):
pass
if __name__ == '__main__':
Test.staticfoo()
Test.clsfoo()
装饰器是一种可调用对象,通常用来增强或替换函数,Python 也支持类装饰器。
def decorator(func):
func()
print('this is decorator')
@decorate
def target():
print('running target()')
本质上,上面的例子与下面的效果是一样的:
def decorator(func):
func()
print('this is decorator')
def target():
print('running target()')
target = decorate(target)
registry = []
def register(func):
print('running register(%s)' % func)
registry.append(func)
return func
@register
def f1():
print('running f1()')
@register
def f2():
print('running f2()')
def f3():
print('running f3()')
def main():
print('running main()')
print('registry ->', registry)
f1()
f2()
f3()
if __name__=='__main__':
main()
执行程序,打印出了:
>>> python3 registration.py running register(<function f1 at 0x100631bf8>) running register(<function f2 at 0x100631c80>) running main() registry -> [<function f1 at 0x100631bf8>, <function f2 at 0x100631c80>] running f1() running f2() running f3() >>> import registration running register(<function f1 at 0x10063b1e0>) running register(<function f2 at 0x10063b268>)
上面的例子说明,装饰器是在模块被导入时立即执行的,而被装饰的函数只在明确调用时才运行。
我们看到当模块一被导入,装饰器中的代码就被执行了,通常我们希望在被装饰方法执行时增强该方法,这样我们显然不希望装饰器代码在模块导入的时机执行。 通过上面闭包的例子我们就可以解决这个问题。
def decorator(func):
def restructure():
func()
print('this is decorator')
return restructure
@decorator
def target():
print('this is target')
上例中,在模块载入时,装饰器被执行,于是 restructure 方法被定义,而由于闭包的特性,restructure 内部可以访问自由变量 func,从而执行对 func 的增强代码。
此前的文章中我们介绍过装饰器模式:
装饰器模式中具体的 Decorator 实现类通过将对组建的请求转发给被装饰的对象,并在转发前后执行一些额外的动作来修改原有的部分行为,实现增强 Component 对象中被请求方法的目的。 装饰器模式是一种十分灵活的,可以动态添加和分离额外操作的设计模式,python 中的装饰器正是因为这个模式而得名,也是实现这个设计模式的得力工具。
装饰器模式的一个典型的应用场景就是对所有需要被监控的方法实现无差别的自动日志打印和监控上报的一些统计功能。 下面的例子展示了函数执行时间的自动打印:
import time
import functiontools
import logging
def clock(func):
@functools.wraps(func)
def clocked(*args):
t0 = time.perf_counter()
result = func(*args)
elapsed = time.perf_counter() - t0
name = func.__name__
arg_str = ', '.join(repr(arg) for arg in args)
logging.info('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
return result
return clocked
上面的例子中,通过装饰器与闭包,实现了对 func 的增强,通过装饰器 clock,自动在 log 中打印了方法的执行时间。 我们定义函数:
@clock
def snooze(seconds):
time.sleep(seconds)
return seconds
if __name__ == '__main__':
print(snooze(.123))
打印出了:
[0.12405610s] snooze(.123) -> 0.123 0.123
functools.wraps 是标准库中的一个装饰器,他把相关的属性从 func 复制到 clocked 中,从而让装饰器的外部表现与被装饰函数的表现林亮一直。
很多时候,我们需要在装饰器上传递一些参数,以实现不同场景的定制化需求,例如有些时候我们希望打印出全部参数、返回值,有些时候需要在发生异常时隐藏异常,返回预设的默认值等。 了解了装饰器的原理以后,包含参数的装饰器就很容易写出来,事实上,无论嵌套多少层,只要记得开始的时候我们提到的装饰器的原理,就都不难理解了。
import inspect
import logging
import time
import uuid
from functools import wraps
def report(project=None, type=None, name=None, raise_exception=True, result=None, paramslog=False, returnlog=False):
def decorator(func):
@wraps(func)
def wrapper(*arg, **kvargs):
cattype = type
if cattype is None:
cattype = inspect.getfile(func)
if project is not None:
cattype = '[:: ' + project + ' ::] ' + cattype
catname = name if name is not None else func.__name__
randid = str(uuid.uuid1())
if paramslog:
logging.info('<%s::%s> [%s] PARAMS: %r; %r' % (cattype, catname, randid, arg, kvargs))
try:
t0 = time.perf_counter()
res = func(*arg, **kvargs)
elapsed = time.perf_counter() - t0
if returnlog:
logging.info('<%s::%s> [%s;%0.8fs] RESULT: %r' % (cattype, catname, randid, elapsed, res))
return res
except Exception as e:
logging.error('<%s::%s> [%s] ERROR: %r' % (cattype, catname, randid, e), exc_info=True)
if raise_exception:
raise e
else:
return result
return wrapper
return decorator