python 中的装饰器及其原理

2019-04-21 23:37:41   最后更新: 2019-04-21 23:37:41   访问数量:103




熟悉 Java 的程序员一定对 Java 中强大的注解有所了解,Python 在一定程度上受到了 Java 的影响,诞生了 Python 的装饰器特性

Python 的装饰器是一个非常强大的功能,本文我们就来详细介绍一下 Python 的装饰器特性

例如我们熟知的类静态方法在定义时就常常会使用 @staticmethod 与 @classmethod 装饰器:

python 面向对象与类及类属性

 

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

 

 

 

装饰器模式中具体的 Decorator 实现类通过将对组建的请求转发给被装饰的对象,并在转发前后执行一些额外的动作来修改原有的部分行为,实现增强 Component 对象中被请求方法的目的

装饰器模式是一种十分灵活的,可以动态添加和分离额外操作的设计模式,python 中的装饰器正是因为这个模式而得名,也是实现这个设计模式的得力工具

 

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

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

 

 

欢迎关注微信公众号,以技术为主,涉及历史、人文等多领域的学习与感悟,每周三到七篇推文,全部原创,只有干货没有鸡汤

 

 






技术帖      python      监控      设计模式      decorator      装饰器     


京ICP备15018585号