Python 装饰器初探

在谈及Python的时候,装饰器一直就是道绕不过去的坎。面试的时候,也经常会被问及装饰器的相关知识。总感觉自己的理解很浅显,不够深刻。是时候做出改变,对Python的装饰器做个全面的了解了。

直接上代码,看看装饰器到底干了些什么?

from functools import wraps
import time

def time_cost(func):
    @wraps(func)
    def f(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)
        end_time = time.time()
        print(end_time - start_time)
    return f

@time_cost
def test(*args, **kwargs):
    time.sleep(1.0)

if __name__ == "__main__":
    test()

上面的Python代码,运行后,会给出test函数的执行时间。代码的执行顺序大概如下,首先是将test作为值传递给time_cost函数,返回函数f,然后再调用f,这是带有time_cost装饰器的test函数的大致执行过程。

从中,不难看出,即使不使用装饰器符号,我们利用Python的语言特性,也能达成上述目的。用装饰器符号的好处是简化了代码,增加了代码的可读性。

这是一段非常简单的对函数使用装饰器的Python代码。等等, @wraps(func)是什么鬼?悄悄干了什么哇?

我们稍微修改下上述代码,结果如下:

from functools import wraps
import time

def time_cost(func):
    def f(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)
        end_time = time.time()
        print(end_time - start_time)

    print('hello world')
    return f

@time_cost
def test(*args, **kwargs):
    time.sleep(1.0)

if __name__ == "__main__":
    print(test.__name__)

发现输出了 hello world,同时输出 test.__name__,居然变成了 f,并不是我们预期的 test。根据这样的输出结果,我们不难得出,其实被装饰器 time_cost修饰过的函数test本质上已经等同于 time_cost(test),此时访问 test.__name__实际上访问的是 time_cost(test).__name__,得到的当然就是 f啦。当我们加上 @wraps(func),此时 test.__name__变成了 test

下面介绍带参数的装饰器,更加难了。在谈论带参数的装饰器之间,首先得引入一个概念,那就” 闭包“。如果你以前用过脚本语言,比如JavaScript,那么一定会很熟悉 闭包这个概念。下面是一个闭包样例

def add(a):
    def wrapper(c):
        return a + c
    return wrapper

if __name__ == "__main__":
    add3 = add(3)
    add9 = add(9)
    print(add3(4) == 7)
    print(add9(1) == 10)

从中可以看出,在调用add3的时候,wrapper内部还可以访问到a的值,这就是闭包的作用。理解了闭包,理解带参数的装饰器就容易多了。

from functools import wraps

def logging(level):
    def outer_wrapper(func):
        @wraps(func)
        def inner_wrapper(*args, **kwargs):
            print("[{level}]: enter function {func}()".format(
                level=level,
                func=func.__name__))
            return func(*args, **kwargs)
        return inner_wrapper
    return outer_wrapper

@logging(level='WARN')
def show(msg):
    print('message:{}'.format(msg))

if __name__ == "__main__":
    show('hello world!')

上面给出了一个带参数装饰器的示例。根据我们前面的铺垫,我们不难分析得出,上面的执行过程是 logging(level='WARN')->outer_wrapper(show)->inner_wrapper(),所以我们可以理解,在被logging修饰后的show其实就是 logging(level='WARN')(show),执行 show('hello world!')其实就是在执行 logging(level='WARN')(show)()。注意与不带参数的装饰器的区别,带参数的装饰器比不带参数的装饰器多套了一层,对应的装饰器也有了调用。因为在使用装饰器的时候,带了括号,所以装饰器本身多套了一层。被装饰器修饰过的函数在被调用的时候,实际上执行的是装饰器最内层的函数,其余层的在函数被修饰时就已经执行了。

你觉得这很自然吗?是的,我之前对装饰者的理解停留在装饰者的深处,没有参数。

[En]

Do you think it’s very natural? Yes, my previous understanding of decorators stays at the depth of decorators without parameters.

依然先上代码

from functools import wraps
import time

class time_cost:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        start_time = time.time()
        result = self.func(*args, **kwargs)
        end_time = time.time()
        print(end_time - start_time)
        return result

@time_cost
def test(*args, **kwargs):
    time.sleep(1.0)

if __name__ == "__main__":
    test()

上面的基于类实现的不带参数的装饰器实际上利用的是Python中的可调用对象特性,凡是实现了 __call__方法的类的实例是可以被调用的。因此被 time_cost修饰过的 test函数本质上已经变成了time_cost类的实例了。调用test方法的时候,实际上执行的是 __call__方法。

下面介绍稍微复杂一点的基于类实现的带有参数的装饰器。

from functools import wraps

class logging:
    def __init__(self, level):
        self.level = level

    def __call__(self, func):

        @wraps(func)
        def wrapper(*args, **kwargs):
            print("[{level}]: enter function {func}()".format(
                level=self.level,
                func=func.__name__))
            return func(*args, **kwargs)
        return wrapper

@logging(level='WARN')
def show(msg):
    print('message:{}'.format(msg))

if __name__ == "__main__":
    show('hello world!')

不同于基于类实现的不带参数的装饰器,基于类实现的带参数的装饰器在 __call__里面多了一层wrapper。被装饰器修饰的show方法本质上是 logging(level='WARN')(show),此时调用show方法,实际上执行的是wrapper方法。

现在看来,这个装饰器并不是很复杂,在实际工程中使用它可以带来很大的便利。

[En]

Now it seems that the decorator is not very complicated, and it can bring great convenience to use the decorator in the actual project.

Original: https://www.cnblogs.com/crackpotisback/p/10197698.html
Author: 狂徒归来
Title: Python 装饰器初探

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/10712/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

免费咨询
免费咨询
扫码关注
扫码关注
联系站长

站长Johngo!

大数据和算法重度研究者!

持续产出大数据、算法、LeetCode干货,以及业界好资源!

2022012703491714

微信来撩,免费咨询:xiaozhu_tec

分享本页
返回顶部