zoukankan      html  css  js  c++  java
  • python 装饰器(四):装饰器基础(三)叠放装饰器,参数化装饰器

    叠放装饰器

    示例 7-19 演示了叠放装饰器的方式:@lru_cache 应用到 @clock 装饰fibonacci 得到的结果上。在示例 7-21 中,模块中最后一个函数应用了两个 @htmlize.register 装饰器。

    把 @d1 和 @d2 两个装饰器按顺序应用到 f 函数上,作用相当于 f =d1(d2(f))。
    也就是说,下述代码:

    @d1
    @d2
    def f():
        print('f')

    等同于:

    def f():
        print('f')
    
    f = d1(d2(f))

    参数化装饰器

    解析源码中的装饰器时,Python 把被装饰的函数作为第一个参数传给装饰器函数。那怎么让装饰器接受其他参数呢?答案是:创建一个装饰器
    工厂函数,把参数传给它,返回一个装饰器,然后再把它应用到要装饰的函数上。不明白什么意思?当然。下面以我们见过的最简单的装饰器
    为例说明:示例 7-22 中的 register。

    示例 7-22 示例 7-2 中 registration.py 模块的删减版,这里再次给出是为了便于讲解

    registry = []
    
    def register(func):
        print('running register(%s)' % func)
        registry.append(func)
        return func
    
    @register
    def f1():
        print('running f1()')
    
    print('running main()')
    print('registry ->', registry)
    f1()

    1 一个参数化的注册装饰器

    为了便于启用或禁用 register 执行的函数注册功能,我们为它提供一个可选的 active 参数,设为 False 时,不注册被装饰的函数。实现方式参见示例 7-23。
    从概念上看,这个新的 register 函数不是装饰器,而是装饰器工厂函数。调用它会返回真正的装饰器,这才是应用到目标函数上的装饰器。

    示例 7-23 为了接受参数,新的 register 装饰器必须作为函数调用

    registry = set()  ➊
    def register(active=True):  ➋
        def decorate(func):  ➌
            print('running register(active=%s)->decorate(%s)'
                  % (active, func))
            if active:   ➍
                registry.add(func)
            else:
                registry.discard(func)  ➎
            return func  ➏
        return decorate  ➐
    @register(active=False)  ➑
    def f1():
        print('running f1()')
    @register()  ➒
    def f2():
        print('running f2()')
    
    def f3():
        print('running f3()')

    ❶ registry 现在是一个 set 对象,这样添加和删除函数的速度更快。
    ❷ register 接受一个可选的关键字参数。
    ❸ decorate 这个内部函数是真正的装饰器;注意,它的参数是一个函
    数。
    ❹ 只有 active 参数的值(从闭包中获取)是 True 时才注册 func。
    ❺ 如果 active 不为真,而且 func 在 registry 中,那么把它删除。
    ❻ decorate 是装饰器,必须返回一个函数。
    ❼ register 是装饰器工厂函数,因此返回 decorate。
    ❽ @register 工厂函数必须作为函数调用,并且传入所需的参数。
    ❾ 即使不传入参数,register 也必须作为函数调用(@register()),即要返回真正的装饰器 decorate。

    这里的关键是,register() 要返回 decorate,然后把它应用到被装饰的函数上。

    示例 7-23 中的代码在 registration_param.py 模块中。如果导入,得到的结果如下:

    >>> import registration_param
    running register(active=False)->decorate(<function f1 at 0x10063c1e0>)
    running register(active=True)->decorate(<function f2 at 0x10063c268>)
    >>> registration_param.registry
    {<function f2 at 0x10063c268>}

    注意,只有 f2 函数在 registry 中;f1 不在其中,因为传给register 装饰器工厂函数的参数是 active=False,所以应用到 f1 上的 decorate 没有把它添加到 registry 中。

    如果不使用 @ 句法,那就要像常规函数那样使用 register;若想把 f添加到 registry 中,则装饰 f 函数的句法是 register()(f);不想添加(或把它删除)的话,句法是 register(active=False)(f)。示例 7-24 演示了如何把函数添加到 registry 中,以及如何从中删除函数。


    示例 7-24 使用示例 7-23 中的 registration_param 模块

    >>> from registration_param import *
    running register(active=False)->decorate(<function f1 at 0x10073c1e0>)
    running register(active=True)->decorate(<function f2 at 0x10073c268>)
    >>> registry  #
    {<function f2 at 0x10073c268>}
    >>> register()(f3)  #
    running register(active=True)->decorate(<function f3 at 0x10073c158>)
    <function f3 at 0x10073c158>
    >>> registry  #
    {<function f3 at 0x10073c158>, <function f2 at 0x10073c268>}
    >>> register(active=False)(f2)  #
    running register(active=False)->decorate(<function f2 at 0x10073c268>)
    <function f2 at 0x10073c268>
    >>> registry  #
    {<function f3 at 0x10073c158>}

    ❶ 导入这个模块时,f2 在 registry 中。
    ❷ register() 表达式返回 decorate,然后把它应用到 f3 上。
    ❸ 前一行把 f3 添加到 registry 中。
    ❹ 这次调用从 registry 中删除 f2。
    ❺ 确认 registry 中只有 f3。

    2 参数化clock装饰器

    本节再次探讨 clock 装饰器,为它添加一个功能:让用户传入一个格式字符串,控制被装饰函数的输出。参见示例 7-25。
    示例 7-25 clockdeco_param.py 模块:参数化 clock 装饰器

    import time
    
    DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}'
    def clock(fmt=DEFAULT_FMT):   ➊
        def decorate(func):       ➋
            def clocked(*_args):  ➌
                t0 = time.time()
                _result = func(*_args)  ➍
                elapsed = time.time() - t0
                name = func.__name__
                args = ', '.join(repr(arg) for arg in _args)  ➎
                result = repr(_result)  ➏
                print(fmt.format(**locals()))  ➐
                return _result  ➑
            return clocked  ➒
        return decorate  ➓
    if __name__ == '__main__':
    
        @clock()
        def snooze(seconds):
            time.sleep(seconds)
    
        for i in range(3):
            snooze(.123)

    ❶ clock 是参数化装饰器工厂函数。
    ❷ decorate 是真正的装饰器。
    ❸ clocked 包装被装饰的函数。
    ❹ _result 是被装饰的函数返回的真正结果。
    ❺ _args 是 clocked 的参数,args 是用于显示的字符串。
    ❻ result 是 _result 的字符串表示形式,用于显示。
    ❼ 这里使用 **locals() 是为了在 fmt 中引用 clocked 的局部变量。
    ❽ clocked 会取代被装饰的函数,因此它应该返回被装饰的函数返回的值。

    ❾ decorate 返回 clocked。
    ❿ clock 返回 decorate。
    ⓫ 在这个模块中测试,不传入参数调用 clock(),因此应用的装饰器使用默认的格式 str。
    在 shell 中运行示例 7-25,会得到下述结果:

    $ python3 clockdeco_param.py
    [0.12412500s] snooze(0.123) -> None
    [0.12411904s] snooze(0.123) -> None
    [0.12410498s] snooze(0.123) -> None

    示例 7-26 和示例 7-27 是另外两个模块,它们使用了 clockdeco_param
    模块中的新功能,随后是两个模块输出的结果。

    示例 7-26 clockdeco_param_demo1.py

    import time
    from clockdeco_param import clock
    
    @clock('{name}: {elapsed}s')
    def snooze(seconds):
        time.sleep(seconds)
    
    for i in range(3):
        snooze(.123)

    示例 7-26 的输出:

    $ python3 clockdeco_param_demo1.py
    snooze: 0.12414693832397461s
    snooze: 0.1241159439086914s
    snooze: 0.12412118911743164s

    示例 7-27 clockdeco_param_demo2.py

    import time
    from clockdeco_param import clock
    
    @clock('{name}({args}) dt={elapsed:0.3f}s')
    def snooze(seconds):
        time.sleep(seconds)
    
    for i in range(3):
        snooze(.123)

    示例 7-27 的输出:

    $ python3 clockdeco_param_demo2.py
    snooze(0.123) dt=0.124s
    snooze(0.123) dt=0.124s
    snooze(0.123) dt=0.124s
  • 相关阅读:
    RecyclerView 下拉刷新上拉加载
    Android 添加、移除和判断 桌面快捷方式图标
    似曾相识的 RecyclerView
    http 需要掌握的知识点(一)
    android 数据存储操作之SQLite
    Android 图片加载[常见开源项目汇总]
    Android 命名规范和编码规范
    线程池及增长策略和拒绝策略
    静态代理和动态代理的区别
    FastJson学习
  • 原文地址:https://www.cnblogs.com/qiu-hua/p/12944694.html
Copyright © 2011-2022 走看看