<返回更多

Python装饰器的细化讲解

2023-02-14  今日头条  当年今时
加入收藏

我叫骆驼

会点儿代码,会点儿读书

这世上的书浩如烟海

我能做的就是尽量整理分享给你

在上一节Flask项目实战第一弹中我们讲到了路由,先看下面代码回顾一下:

from flask import Flask

App = Flask(__name__)


@app.route("/hello")
def hello():
    return "<p>Hello World ...</p>"


if __name__ == '__main__':
    app.run(load_dotenv=True)

@app.route("/hello") 就是装饰器。交流群里小伙伴问我,Python/ target=_blank class=infotextkey>Python里的装饰器该怎么理解,今天我们好好唠唠这个东西。

说到装饰器,我们不得不谈一个知识点:闭包。我们从代码入手,一点一点来说闭包。

Python 有一个好玩的地儿,就是 def 函数(exterior)内部可以嵌套另一个 def 函数(interior)。调用 exterior 时,若遇到 interior , 仅仅完成对于 interior 的定义,而不去运行 interior。如果 exterior return interior,那么我们可以使用 interior () 去调用 内部函数 interior 函数。

 var = 0


def exterior():
    var = 1

    def interior():
        print(var)
    return interior()  # 这里返回 interior 函数调用结果


exterior() # 打印 1

从上面代码和结果中可以看到,interior 打印的 var 值 并非 第一行的 var。这说明,exterior 中的嵌套变量 var 覆盖了全局变量var=0,然后 interior 中的本地变量按照引用规则,就引用了var = 1。

接下来,我们仔细想想下面这句话:

interior 作用域在函数结束后就立即失效,而exterior嵌套作用域在 interior 的函数返回后却仍然有效。

var = 0


def exterior():
    var = 1

    def interior():
        print(var)
    return interior  # 这里返回 interior 函数对象


inter = exterior() 
inter()  # 打印 1

看完上面代码,再思考一下刚刚的话。如果还不清楚,看下图:

图解

创建一个闭包必须满足以下几点:

现在有了闭包的知识点,我们再聊聊装饰器(decorator)。我要掰开了揉碎了来说说装饰器。

刚刚接触装饰器的同学会对这个概念感到迷茫,然后你在网上(尤其 csdn)找例子或者教程,基本千篇一律,或者讲解的“点到为止”,你在看完之后,或许更迷茫了。

函数是什么

在说装饰器前,我们聊聊 Python 的函数。众所周知:在 Python 中,一切皆对象,函数是一等对象。

编程语言理论家把“一等对象”定义为满足下述条件的程序实体:

我们看这么一段程序:

def double(x: int) -> int:
    return x * 2

这段代码很简单,计算了一个整数的2倍。那么我么用 dis 模块进行反编译,看看他是怎么运行的。

>>>from my_test import double
>>>from dis import dis

>>>dis(double)  # 结果如下

源码行号

指令在函数中的偏移

指令符号

指令参数

实际参数值

2

0

LOAD_FAST

0

x

 

2

LOAD_CONST

1

2

 

4

BINARY_MULTIRLY

 

 

 

6

RETURN_VALUE

 

 

指令符号解释:

结合反编译的结果,仔细理解一下代码的运行流程。下面我们看另外一个例子:

def double(x: int) -> int:
    return x * 2


def triple(x: int) -> int:
    return x * 3


def call_func(func, x: int) -> int:
    return func(x)


result = call_func(triple, 2)
print(result)
dis(call_func)

源码行号

指令在函数中的偏移

指令符号

指令参数

实际参数值

10

0

LOAD_FAST

0

func

 

2

LOAD_FAST

1

x

 

4

CALL_FUNCTION

1

 

 

6

RETURN_VALUE

 

 

在运行过程中:出现了 CALL_FUNCTION 。结合第13行代码,仔细体会一下这句话:函数能作为参数传递给另外一个函数。

我们现在看一下闭包的执行流程

def call_func():
    def double(x: int) -> int:
        return x * 2

    return double

dis(call_func)

里边出现了一个关键词:MAKE_FUNCTION,见名知意,创建函数。此时再回想“一等对象”所满足的条件。

说了这么多,无非是想告诉大家一个重要的东西,函数就是对象,可以被另一个函数返回,可以被赋值,也可以被调用。

其实到这里,才真是说完闭包这个东西。装饰器和闭包大同小异,下面我们接着来。

装饰器

有这种一种等价语法:

def callfunc(func):
    return 1


@callfunc
def triple(x: int) -> int:
    return x * 3

等价于

def callfunc(func):
    return 1


def triple(x: int) -> int:
    return x * 3


triple = callfunc(triple)

无论上面那种方式,我们输出的 tripre 这个对象的值都是 1

>>> print(triple)
>>> 1

所以,闭包可以写成@这种形式呢?其实,装饰器可以理解为闭包的一种,我们可以这样认为:闭包传递的是变量,而装饰器传递的是函数,除此之外没有任何区别。

我们看一个打印时间的装饰器:

import time


def timeit(func):
    def wrapper(x):
        start = time.time()
        ret = func(x)
        print(time.time() - start)
        return ret

    return wrapper


@timeit
def my_func(x):
    time.sleep(x)


my_func(1)

timeit 装饰器就打印 my_func 函数的运行时间。是不是在了解完闭包之后很简单了。

装饰器的作用就是:在不改变原函数的情况下,对已有函数进行额外的功能扩展。

恭喜你,Python 技能又进一步。

回到 Flask 我们看看路由装饰器

Flask 中路由的装饰器很简单,我们以 route 为例,以下是 route 函数源码(抽离版):

import typing as t


def add_url_rule(rule, endpoint, f, param):
    pass


def route(rule: str, **options: t.Any) -> t.Callable:

    def decorator(f: t.Callable) -> t.Callable:
        endpoint = options.pop("endpoint", None)
        add_url_rule(rule, endpoint, f, **options)
        return f

    return decorator

route 函数就是一个装饰器,内部嗲用 add_url_rule 实现真正的路由添加。再回过头看看装饰器的作用和定义以及使用,是不是明白了许多!加油,慢就快,快就是慢。

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>