前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python 中的装饰器及其原理

python 中的装饰器及其原理

作者头像
用户3147702
发布2022-06-27 13:27:15
5680
发布2022-06-27 13:27:15
举报
文章被收录于专栏:小脑斧科技博客

1. 引言

熟悉 Java 的程序员一定对 Java 中强大的注解有所了解,Python 在一定程度上受到了 Java 的影响,诞生了 Python 的装饰器特性。 Python 的装饰器是一个非常强大的功能,本文我们就来详细介绍一下 Python 的装饰器特性。 例如我们熟知的类静态方法在定义时就常常会使用 @staticmethod 与 @classmethod 装饰器:

代码语言:javascript
复制
class TestClass:
    @staticmethod
    def staticfoo():
        pass

    @classmethod
    def clsfoo(cls):
        pass

if __name__ == '__main__':
    Test.staticfoo()
    Test.clsfoo()

2. 装饰器

装饰器是一种可调用对象,通常用来增强或替换函数,Python 也支持类装饰器。

代码语言:javascript
复制
def decorator(func):
    func()
    print('this is decorator')

@decorate
def target():
    print('running target()')

2.1. 装饰器的原理

本质上,上面的例子与下面的效果是一样的:

代码语言:javascript
复制
def decorator(func):
    func()
    print('this is decorator')

def target():
    print('running target()')

target = decorate(target)

3. 装饰器的执行时机

代码语言:javascript
复制
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>)

上面的例子说明,装饰器是在模块被导入时立即执行的,而被装饰的函数只在明确调用时才运行。

4. 闭包与装饰器

我们看到当模块一被导入,装饰器中的代码就被执行了,通常我们希望在被装饰方法执行时增强该方法,这样我们显然不希望装饰器代码在模块导入的时机执行。 通过上面闭包的例子我们就可以解决这个问题。

代码语言:javascript
复制
def decorator(func):
    def restructure():    
        func()
        print('this is decorator')
    return restructure

@decorator
def target():
    print('this is target')

上例中,在模块载入时,装饰器被执行,于是 restructure 方法被定义,而由于闭包的特性,restructure 内部可以访问自由变量 func,从而执行对 func 的增强代码。

5. 通过装饰器实现装饰器模式

5.1. 装饰器模式

此前的文章中我们介绍过装饰器模式:

装饰器模式中具体的 Decorator 实现类通过将对组建的请求转发给被装饰的对象,并在转发前后执行一些额外的动作来修改原有的部分行为,实现增强 Component 对象中被请求方法的目的。 装饰器模式是一种十分灵活的,可以动态添加和分离额外操作的设计模式,python 中的装饰器正是因为这个模式而得名,也是实现这个设计模式的得力工具。

5.2. python 装饰器实现自动监控

装饰器模式的一个典型的应用场景就是对所有需要被监控的方法实现无差别的自动日志打印和监控上报的一些统计功能。 下面的例子展示了函数执行时间的自动打印:

代码语言:javascript
复制
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 中打印了方法的执行时间。 我们定义函数:

代码语言:javascript
复制
@clock
def snooze(seconds):
    time.sleep(seconds)
    return seconds

if __name__ == '__main__':
    print(snooze(.123))

打印出了:

[0.12405610s] snooze(.123) -> 0.123 0.123

5.2.1. functools.wraps

functools.wraps 是标准库中的一个装饰器,他把相关的属性从 func 复制到 clocked 中,从而让装饰器的外部表现与被装饰函数的表现林亮一直。

5.3. 监控优化 — 增加参数

很多时候,我们需要在装饰器上传递一些参数,以实现不同场景的定制化需求,例如有些时候我们希望打印出全部参数、返回值,有些时候需要在发生异常时隐藏异常,返回预设的默认值等。 了解了装饰器的原理以后,包含参数的装饰器就很容易写出来,事实上,无论嵌套多少层,只要记得开始的时候我们提到的装饰器的原理,就都不难理解了。

代码语言:javascript
复制
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
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-04-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 小脑斧科技博客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 引言
  • 2. 装饰器
    • 2.1. 装饰器的原理
    • 3. 装饰器的执行时机
    • 4. 闭包与装饰器
    • 5. 通过装饰器实现装饰器模式
      • 5.1. 装饰器模式
        • 5.2. python 装饰器实现自动监控
          • 5.2.1. functools.wraps
        • 5.3. 监控优化 — 增加参数
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档