Python之装饰器

装饰器的简单使用。

基础知识

装饰器是可调用的对象,其参数是另一个函数(被装饰的函数)。装饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。

特性:

  1. 能把被装饰的函数替换成其他函数

    def wrapper(func):
        def inner(*args, **kwargs):
            print("hello, i am inner")
            ret = func(*args, **kwargs)
            return ret
    
        return inner
    
    @wrapper
    def fun():
        print("hello world, i am fun")

    运行结果:

    >>> fun()
    hello, i am inner
    hello world, i am fun
    >>> fun
    <function wrapper.<locals>.inner at 0x000001467E56AAF8>

    结论:调用被装饰的 fun 其实会运行 inner, 由最后结果可以发现 fun 现在是 inner 的引用

  1. 在被装饰的函数定义之后立即运行。这通常是在导入时(即 Python 加载模块时)

    创建test_wrap.py文件,并写入:

    def test_wrap(func):
        print("it is run test_wrap")
        def wrap(*args, **kwargs):
            print("it is run wrap")
            return func(*args, **kwargs)
        return wrap
    
    @test_wrap
    def test():
        print('run complete test()')
if __name__ == '__main__':
    print('running test()')
    test()
```

运行脚本并返回结果:`python test_wrap.py`

```shell
it is run test_wrap
running test()
it is run wrap
run complete test()
```

结论:函数装饰器在导入模块时立即执行,而被装饰的函数只在明确调用时运行。

参数化装饰器

创建一个装饰器工厂函数,把参数传给它,返回一个装饰器,然后再把它应用到要装饰的函数上。

def test_params_wrap(v):
    def test_wrap(func):
        def wrap(*args, **kwargs):
            print(v)
            return func(*args, **kwargs)
        return wrap
    return test_wrap


@test_params_wrap("hello test_params")
def run():
    print("run")


if __name__ == '__main__':
    run()

运行结果:

hello test_params
run

多个装饰器

看个示例:

def d1(func):
    def inner():
        print("执行了d1 func")
        return func()
    return inner


def d2(func):
    def inner():
        print("执行了 d2 func")
        return func()
    return inner

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

if __name__ == '__main__':
    f()
    # result:
    # 执行了d1 func
    # 执行了 d2 func
    # f

结论: 把 @d1@d2 两个装饰器按顺序应用到 f 函数上,作用相当于 f =d1(d2(f))。换句话说:多个装饰器装饰同一个函数时,执行顺序是从上到下的。

@wraps的作用

@wraps(func)的作用: 不改变使用装饰器原有函数的结构(如name, doc)。

  1. 不加@wraps(func)的装饰器

    def d1(func):
        def inner():
            """ 我是inner 函数 """
            print("执行了d1 func")
            return func()
        return inner
@d1
def f():
    """ 我是f函数 """
    print("f")


if __name__ == '__main__':
    f()
    print("f name: ", f.__name__, ",f doc: ", f.__doc__)

    # result: 
    # 执行了d1 func
    # f
    # f name:  inner ,f doc:   我是inner 函数
```

结论: 返回的是装饰器的名字和注释,虽不影响程序的运行,但后面查问题或者做其它总归不好。
  1. @wraps(func)的装饰器

    from functools import wraps
    def d1(func):
        @wraps(func)
        def inner():
            """ 我是inner 函数 """
            print("执行了d1 func")
            return func()
        return inner
    
    @d1
    def f():
        """ 我是f函数 """
        print("f")
if __name__ == '__main__':
    f()
    print("f name: ", f.__name__, ",f doc: ", f.__doc__)

    # result:
    # 执行了d1 func
    # f
    # f name:  f ,f doc:   我是f函数
```

结论:不改变原函数的名字和注释等

参考书籍: Fluent Python