一日一技:Python装饰器的执行顺序

简介: 一日一技:Python装饰器的执行顺序

说到Python装饰器的执行顺序,有很多半吊子张口就来:

靠近函数名的装饰器先执行,远离函数名的装饰器后执行。

这种说法是不准确的。但是这些半吊子多半还会不服,他们会甩出一段代码给你,来『证明』自己的观点:

def decorator_outer(func):
    print("我是外层装饰器")
    def wrapper():
        func()
    return wrapper
def decorator_inner(func):
    print("我是内层装饰器")
    def wrapper():
        func()
    return wrapper  
@decorator_outer
@decorator_inner
def func():
    print("我是函数本身")
func()

运行效果如下图所示:

decorator_inner这个装饰器靠近函数名,是内层装饰器,它里面的print先打印出来;decorator_outer远离函数名,是外层装饰器,它里面的print打印出来。看起来确实是内层装饰器先执行,外层装饰器后执行

为什么我说这种看法是不准确呢?我们来看看下面这段代码:

def decorator_outer(func):
    print("我是外层装饰器")
    print('a')
    print('b')
    def wrapper():
        print('外层装饰器,函数运行之前')
        func()
        print('外层装饰器,函数运行之后')
    print('外层装饰器闭包初始化完毕')
    print('c')
    print('d')
    return wrapper
def decorator_inner(func):
    print("我是内层装饰器")
    print(1)
    print(2)
    def wrapper():
        print('内层装饰器,函数运行之前')
        func()
        print('内层装饰器,函数运行之后')
    print('内层装饰器闭包初始化完毕')
    print(3)
    print(4)
    return wrapper  
@decorator_outer
@decorator_inner
def func():
    print("我是函数本身")
func()

上面这个代码的运行效果如下图所示:

从图中可以看到,装饰器里面的代码中,wrapper闭包外面的代码确实是内层装饰器先执行,外层装饰器后执行。但是在闭包wrapper内部的代码,却稍微复杂一些:

  1. 外层装饰器先执行,但只执行了一部分,执行到调用func()
  2. 内层装饰器开始执行
  3. 内层装饰器执行完
  4. 外层装饰器执行完

这个执行效果有点类似于:

def func():
    print('我是函数本身')
def deco_inner():
    print('内层装饰器,函数运行之前')
    func()
    print('内层装饰器,函数运行之后')
def deco_outer():
    print('外层装饰器,函数运行之前')
    deco_inner()
    print('外层装饰器,函数运行之后')

运行效果如下图所示,跟装饰器里面各个wrapper闭包的运行顺序是一致的。

所以,当我们说多个装饰器堆叠的时候,哪个装饰器的代码先运行时,不能一概而论说内层装饰器的代码先运行。这会给人一种错觉,认为是内层装饰器的代码从第一行到最后一行都是先运行的。准确的说法应该是,wrapper外面的代码,确实是内层装饰器先运行,外层装饰器后运行。但是wrapper里面的代码,是外层装饰器先开始运行,后运行完毕,内层装饰器后开始运行,先运行完毕

这个知识看起来似乎有点像面试八股文,有什么用呢?我给大家举个例子。下面是使用FastAPI写的一个接口:

from fastapi import FastAPI
app = FastAPI()
def do_query_dataset(dataset_id):
    print("直接读取数据库,获取dataset信息")
    dataset_info = {"xxx": 1, "yyy": 2}
    return dataset_info
@app.get('/dataset')
def get_dataset(dataset_id: int):
    dataset_info = do_query_dataset(dataset_id)
    return {'success': True, "data": dataset_info}

用户访问这个接口,URL中传入参数dataset_id,就可以获得数据集的信息。如下图所示:

现在,要增加权限校验,首先要判断用户是否登录。在用户已经登录的情况下,看这个用户是否有这个数据集的权限。在有这个数据集的权限时,才能返回数据集信息。

你肯定想到了使用装饰器来做这两步,一开始你写的代码可能是这样的:

def check_login(func):
    def wrapper(*args, **kwargs):
        print('检测是否有特定的Cookies')
        is_login = False
        if not is_login:
            return {'success': False, "msg": "没有登录"}
        return func(*args, **kwargs)
    return wrapper
def check_data_set_permission(func):
    def wrapper(*args, **kwargs):
        print('检测是否有特定的数据集权限')
        print('首先从请求参数中获取dataset_id')
        print('然后从登录session中获取用户id,注意,如果没有登录,是没有session的')
        print('判断用户是否有这个dataset的权限')
        has_data_set_permission = True
        if not has_data_set_permission:
            return {'success': False, "msg": "没有数据集权限"}
        return func(*args, **kwargs)
    return wrapper

这个时候,我们要确保check_login里面检查用户是否登录的代码首先运行。然后才能是check_data_set_permission里面检查数据集权限的代码。

本文开头的半吊子,认为靠近函数名的装饰器先执行,远离函数名的装饰器后执行。按他们理论,就会写成:

@check_data_set_permission
@check_login
def do_query_dataset(dataset_id):
    ...

这样写显然是错误的。因为check_data_set_permission装饰器会有一个前提,就是用户已经登录了,代码才会走到这里。那么他就会直接去session取用户ID。没有登录的用户是没有用户ID的。在取ID的这一步就会出错。

根据本文上面的解释,由于这两个逻辑都是在wrapper内部的。 wrapper内部的代码,外层装饰器先开始运行。因此,这里我们装饰器的正确顺序,只能按照如下顺序排列:

@check_login
@check_data_set_permission
def do_query_dataset(dataset_id):
    ...

这个写法,从直觉上,就会跟本文开头的认知矛盾。但这才是正确的顺序。

目录
相关文章
|
7天前
|
缓存 测试技术 Python
Python装饰器:优雅地增强函数功能
Python装饰器:优雅地增强函数功能
157 99
|
7天前
|
存储 缓存 测试技术
Python装饰器:优雅地增强函数功能
Python装饰器:优雅地增强函数功能
141 98
|
11天前
|
缓存 Python
Python中的装饰器:优雅地增强函数功能
Python中的装饰器:优雅地增强函数功能
|
21天前
|
存储 缓存 测试技术
理解Python装饰器:简化代码的强大工具
理解Python装饰器:简化代码的强大工具
|
1月前
|
程序员 测试技术 开发者
Python装饰器:简化代码的强大工具
Python装饰器:简化代码的强大工具
156 92
|
3月前
|
Python
掌握Python装饰器:轻松统计函数执行时间
掌握Python装饰器:轻松统计函数执行时间
270 76
|
26天前
|
设计模式 缓存 运维
Python装饰器实战场景解析:从原理到应用的10个经典案例
Python装饰器是函数式编程的精华,通过10个实战场景,从日志记录、权限验证到插件系统,全面解析其应用。掌握装饰器,让代码更优雅、灵活,提升开发效率。
87 0
|
4月前
|
人工智能 API Python
掌握 Python 文件处理、并行处理和装饰器
本文介绍了 Python 在文件处理、并行处理以及高级功能(如装饰器、Lambda 函数和推导式)的应用。第一部分讲解了文件的基本操作、读写方法及处理大型文件的技巧,并演示了使用 Pandas 处理结构化数据的方式。第二部分探讨了多线程与多进程的并行处理,以及 `concurrent.futures` 模块的简化用法,适合不同类型的任务需求。第三部分则深入装饰器的实现与应用,包括简单装饰器、带参数的装饰器及 `functools.wraps` 的使用,同时简要介绍了 Lambda 函数和推导式的语法与场景。内容实用且全面,帮助读者掌握 Python 高效编程的核心技能。
|
9月前
|
测试技术 数据安全/隐私保护 开发者
探索Python中的装饰器:从基础到高级应用
装饰器在Python中是一个强大且令人兴奋的功能,它允许开发者在不修改原有函数代码的前提下增加额外的功能。本文将通过具体代码示例,带领读者从装饰器的基础概念入手,逐步深入到高级用法,如带参数的装饰器和装饰器嵌套等。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和技巧。
109 6

推荐镜像

更多