美文网首页
Python 装饰器深度解析

Python 装饰器深度解析

作者: SingleDiego | 来源:发表于2020-06-17 10:45 被阅读0次

参考:https://zhuanlan.zhihu.com/p/45458873




假设现在有一个 add 求和函数,想要统计他的运行时长,最简单的方法可以这样:

import time

def add(a, b):
    start = time.time()
    print(a + b)
    # 模拟耗时操作
    time.sleep(0.1)
    long = time.time() - start
    print('共耗时{}秒'.format(long))

add(1, 1)

这样做可以实现需求,但是对原函数做了修改,不仅增加了耦合性,扩展和复用也变得难以实现。

假如我想对其他函数也进行运行时长统计,就需要一个不改变原来函数而且可以复用的新函数来实现:

import time

def timer(func, *args):
    start = time.time()
    func(*args)
    # 模拟耗时操作
    time.sleep(0.1)
    long = time.time() - start
    print('共耗时{}秒'.format(long))

def add(a, b):
    print(a + b)

timer(add, 1, 1)

这样没有改变原函数,但是改变了函数调用方式,每个调用 add 的地方都需要修改,这么做只是转嫁了矛盾而已。

不能修改原函数,又不能改变调用方式,那该怎么办呢?装饰器是时候登场了。




在写装饰器之前先了解两个概念:高阶函数和闭包

高阶函数:接受函数为入参,或者把函数作为结果返回的函数。后者称之为嵌套函数。

闭包:指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。概念比较晦涩,简单来说就是嵌套函数引用了外层函数的变量。

嵌套函数和闭包可以理解为是同时存在的,上面的 timer 已经是高阶函数了,它接受函数作为入参,我们把它改造为嵌套函数实现装饰器:

import time

def timer(func, *args):
    def wrapper(*args, **kwargs):
        start = time.time()
        # 此处拿到了被装饰的函数 func
        func(*args, **kwargs)
        # 模拟耗时操作
        time.sleep(0.1)
        long = time.time() - start
        print('共耗时{}秒'.format(long))
    # 返回内层函数的引用
    return wrapper 

@timer
def add(a, b):
    print(a + b)

add(1, 2)

timer 被我们改造成了装饰器,它接受被装饰函数为入参,返回内部嵌套函数的引用(注意:此处并未执行函数),内部嵌套函数 wrapper 持有被装饰函数的引用即 func

@ 是 Python的 语法糖,它的作用类似于:

# 此处返回的是 timer.<locals>.wrapper 函数引用
add = timer(add) 
add(1, 2)

装饰器的加载到执行的流程:

    1. 模块加载
    1. 遇到 @,执行timer函数,传入 add 函数
    1. 生成 timer.<locals>.wrapper 函数并命名为 add,其实是覆盖了原同名函数
    1. 调用 add(1, 2)
    1. 去执行 timer.<locals>.wrapper(1, 2)
    1. wrapper 内部持有原 add 函数引用(func),调用 func(1, 2)
    1. 继续执行完 wrapper 函数




如果存在多个装饰器,执行顺序是什么样的呢?

def test1(func):
    def wrapper(*args, **kwargs):
        print('before test1 ...')
        func(*args, **kwargs)
        print('after test1 ...')
    return wrapper 

def test2(func):
    def wrapper(*args, **kwargs):
        print('before test2 ...')
        func(*args, **kwargs)
        print('after test2 ...')
    return wrapper 

@test2
@test1
def add(a, b):
    print(a + b)

add(1, 2)

输出结果:

before test2 ...
before test1 ...
3
after test1 ...
after test2 ...

如果把 add 函数比喻为圆心,test1 为近心端,test2 为远心端,那么执行的过程就好比一颗子弹从远心端沿着直径的轨迹穿过圆心再从远心端穿出。

再形象一点,可以把装饰器想象成洋葱,由近及远对函数进行层层包裹,执行的时候就是拿一把刀从一侧开始切,直到切到另一侧结束。




理解了装饰器之后,我们可以思考一下,带参数的装饰器该怎么写呢?

我们知道装饰器最终返回的是嵌套函数的引用,只要记住这点,装饰器就任由我们发挥了。写一个带参数的装饰器:

import time

def timer(second): # 传入装饰器的参数
    def _timer(func): # 传入要装饰的函数
        def wrapper(*args, **kwargs): # 被装饰的函数的参数
            start = time.time()
            func(*args, **kwargs)
            # 模拟耗时操作
            time.sleep(second)
            long = (time.time() - start)
            print('共耗时{}秒'.format(long))
        return wrapper
    return _timer 

@timer(0.1)
def add(a, b):
    print(a + b)

add(1, 2)

上例演示了一个可以用参数设定暂停时间的装饰器。

可能会有人有疑问,经过装饰器之后的函数还是原来的函数吗?原来的函数肯定还存在的,只不过真正调用的是装饰后生成的新函数。

那岂不是打破了“不能修改原函数”的规则?

是的,看下面的示例:

>>> print(add)
<function timer.<locals>._timer.<locals>.wrapper at 0x02EDAA50>

>>> print(add.__name__)
wrapper

>>> print(add.__doc__)
None

为了消除装饰器对原函数的影响,我们需要伪装成原函数,拥有原函数的属性,我们使用 Python 的 functools 模块来实现。

import time, functools

def timer(second):
    def _timer(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            start = time.time()
            # 此处拿到了被装饰的函数 func
            func(*args, **kwargs)
            # 模拟耗时操作
            time.sleep(second)
            long = (time.time() - start)
            print('共耗时{}秒'.format(long))
        return wrapper
    return _timer 

@timer(0.1)
def add(a, b):
    print(a + b)

add(1, 2)
>>> print(add)
<function add at 0x02EFAA50>

>>> print(add.__name__)
add

>>> print(add.__doc__)
None

functools.wraps 对我们的装饰器函数进行了装饰之后,add 表面上看起来还是 add

相关文章

  • Python 装饰器深度解析

    参考:https://zhuanlan.zhihu.com/p/45458873 假设现在有一个 add 求和函数...

  • Python装饰器解析

    和生成器一样,装饰器也是Python独有的概念,面试中非常容易被考察到。这篇文章从以下角度尝试解析Python装饰...

  • 装饰器模式

    介绍 在python装饰器学习 这篇文章中,介绍了python 中的装饰器,python内置了对装饰器的支持。面向...

  • python中的装饰器

    python装饰器详解 Python装饰器学习(九步入门) 装饰器(decorator) 就是一个包装机(wrap...

  • [译] Python装饰器Part II:装饰器参数

    这是Python装饰器讲解的第二部分,上一篇:Python装饰器Part I:装饰器简介 回顾:不带参数的装饰器 ...

  • Python中的装饰器

    Python中的装饰器 不带参数的装饰器 带参数的装饰器 类装饰器 functools.wraps 使用装饰器极大...

  • Python进阶——面向对象

    1. Python中的@property   @property是python自带的装饰器,装饰器(decorat...

  • Python 装饰器填坑指南 | 最常见的报错信息、原因和解决方

    Python 装饰器简介装饰器(Decorator)是 Python 非常实用的一个语法糖功能。装饰器本质是一种返...

  • Python装饰器

    Python装饰器 一、函数装饰器 1.无参装饰器 示例:日志记录装饰器 2.带参装饰器 示例: 二、类装饰器 示例:

  • python3基础---详解装饰器

    1、装饰器原理 2、装饰器语法 3、装饰器执行的时间 装饰器在Python解释器执行的时候,就会进行自动装饰,并不...

网友评论

      本文标题:Python 装饰器深度解析

      本文链接:https://www.haomeiwen.com/subject/bsdjxktx.html