一句话说清楚多个装饰器的执行顺序

本文通过实例详细解析了Python中多个装饰器的执行顺序。理解装饰器如何一层层包裹目标函数,以及装饰器间的执行流程。
部署运行你感兴趣的模型镜像

要理清楚多个装饰器的执行顺序,对于很多人来说是噩梦一般的存在,但是,这个其实一句话就能说明白的,先看下面代码:

def out_1(func):
    print('i am out_1')

    def inner_1():
        print('i am inner_1')
        return func()
    return inner_1


def out_2(func):
    print('i am out_2')

    def inner_2():
        print('i am inner_2')
        return func()
    return inner_2


@out_1
@out_2
def func_print():
    print('i am func_print')

func_print()

定义了两个装饰器函数,out1,out2,执行结果如下:

i am out_2
i am out_1
i am inner_1
i am inner_2
i am func_print

好吧,一句话这么理解:装饰器,只对函数进行装饰,装饰器不对装饰器进行装饰,所以

1~~~~out2先装饰func_print函数,相当于执行func_print = out_2(func_print),所以先打印了 i am out_2,

2~~~~@out_2装饰完之后,@out1再对func_print进行装饰,并且此时的func_print已经指向了inner_2函数,所以@out_1对他进行装饰相当于func_print= out1(inner_2)这个时候就会执行"i am out_1"的打印,

3~~~~此时的func_print就指向了inner_1,就是两个装饰都装饰完毕了,最后一行代码调用了func_print(),所以就相当于调用了inner_1,这个时候打印"i am inner_1"

4~~~~打印完成之后执行到inner_1里的return func() 此时的func是inner_2,再执行inner_2打印‘I am inner_2’,

5~~~~而inner_2里还有return fun(),这时的func才是最初的func_print,最后打印‘i am func_print’

以上描述看似复杂,但是你如果能理解这个黑体加粗的话,装饰器装饰顺序就是so easy!!!!!有木有


您可能感兴趣的与本文相关的镜像

Llama Factory

Llama Factory

模型微调
LLama-Factory

LLaMA Factory 是一个简单易用且高效的大型语言模型(Large Language Model)训练与微调平台。通过 LLaMA Factory,可以在无需编写任何代码的前提下,在本地完成上百种预训练模型的微调

好的,我明白了你的需求:**不删除你原来的任何注释,只在你原有代码的基础上,补充明性的中文注释**,帮助你更清晰地理解夹具的含义和执行流程。 以下是你的原始代码,**完全保留你原有的注释内容**,并**新增了逐行的中文解释性注释**,帮助你彻底理解夹具是如何工作的。 --- ```python # ---------------------------------- # 2.1 基础夹具模式 # ---------------------------------- import pytest import requests # 定义个夹具函数:api_client # 🛠️ 这是个“基础API客户端”,用于为测试函数提供预设的客户端配置 # 默认作用域是函数级(function scope),即每次测试函数调用它时都会重新执行次 # 返回值是个包含 base_url 的字典 @pytest.fixture def api_client(): """ 🛠️ 通用API客户端(函数级作用域) - 返回个包含基础URL的字典 - 可供多个测试函数复用 - 每次测试函数调用都会创建个新的实例 """ return {"base_url": "http://127.0.0.1:5000"} # 测试函数 test_with_client 使用了夹具 api_client # pytest 会自动将夹具返回的值注入到测试函数中 def test_with_client(api_client): """ ✅ 夹具自动注入演示 - api_client 是通过 fixture 提供的 - 自动注入到测试函数中 - 用于发送GET请求验证服务是否运行 """ response = requests.get(f"{api_client['base_url']}/get") assert response.status_code == 200 # ---------------------------------- # 2.2 进阶夹具技巧(适配本地服务) # ---------------------------------- # 定义个夹具 auth_token,依赖于 api_client # scope="module" 表示该夹具在整个测试模块(文件)中只执行次 # 通常用于耗时操作(如登录认证),避免重复执行 @pytest.fixture(scope="module") def auth_token(api_client): """ 🔐 模拟认证令牌(模块级夹具) - scope="module":整个测试模块共享个实例 - 模拟认证流程,返回个模拟的 token - 用于后续需要认证的测试 """ return "simulated_token_12345" # 定义个夹具 authenticated_client,依赖于 api_client 和 auth_token # 它组合了这两个夹具的结果,构建出个带认证头的客户端 # 每次调用该夹具时都会执行次,是函数级作用域 @pytest.fixture def authenticated_client(api_client, auth_token): """ 🔒 带认证的客户端(组合夹具) - 组合使用 api_client 和 auth_token - 添加 Authorization 请求头 - 用于需要认证的接口测试 # 🧠 拷贝知识(零基础必读) ## 浅拷贝(当前使用) client = api_client.copy() → 创建新对象,但仅复制第层内容 ✔️ 优点:速度快,内存占用小 ✔️ 适用场景:对象只有简单属性(如当前只有headers) ❌ 风险:若对象包含嵌套结构(如headers里有字典),内层数据仍共享 ## 深拷贝(备选方案) import copy client = copy.deepcopy(api_client) → 完全独立的新对象 ✔️ 优点:彻底隔离,绝对安全 ❌ 缺点:速度慢,内存消耗大 ✔️ 适用场景:对象有多层嵌套数据(如配置字典含子字典) # 当前选择浅拷贝的原因: 1. api_client通常是简单结构(如 {base_url, headers}) 2. headers属性我们直接覆盖替换,不修改原有内容 3. 高性能需求(测试执行速度优先) """ # 创建新对象(隔离原始api_client) client = api_client.copy() # 📌 浅拷贝操作 # 添加认证头(不影响原始api_client) client["headers"] = {"Authorization": f"Bearer {auth_token}"} return client # =========================================================================== # 测试用例(展示实际调用流程) # =========================================================================== # 测试1:调用公共API(不需要认证) # 使用夹具:api_client # 每次调用都会创建个新的 api_client 实例 def test_public_api(api_client): """🌐 测试公共API(无需认证) 输出: [创建API客户端] → 函数级作用域(每个测试独立) [测试1] 调用公共API 使用客户端: {'base_url': 'http://127.0.0.1:5000'} """ print("\n[测试1] 调用公共API") print(f" 使用客户端: {api_client}") assert "base_url" in api_client # 测试2:调用私有API1(需要认证) # 使用夹具:authenticated_client # 它会自动触发依赖的 api_client 和 auth_token 夹具 def test_private_api1(authenticated_client): """🔒 测试私有API1(需要认证) 输出: [创建API客户端] → (新实例) === 生成认证令牌 === → (首次调用模块级夹具) 使用API客户端: http://127.0.0.1:5000 [构建认证客户端] → 原始API客户端ID: 140000000 | 新客户端ID: 140000001 [测试2] 调用私有API1 认证客户端: {'base_url': 'http://127.0.0.1:5000', 'headers': {...}} """ print("\n[测试2] 调用私有API1") print(f" 认证客户端: {authenticated_client}") assert "headers" in authenticated_client # 测试3:调用私有API2(需要认证) # 同样使用 authenticated_client # auth_token 已经在上个测试中创建,这里会复用 def test_private_api2(authenticated_client): """🔒 测试私有API2(需要认证) 输出: [创建API客户端] → (新实例) [构建认证客户端] → (重用auth_token) 原始API客户端ID: 140000002 | 新客户端ID: 140000003 [测试3] 调用私有API2 认证客户端: {'base_url': 'http://127.0.0.1:5000', 'headers': {...}} """ print("\n[测试3] 调用私有API2") print(f" 认证客户端: {authenticated_client}") authenticated_client["test_flag"] = True # 修改副本 # 测试4:再次测试公共API(验证状态隔离) # 使用 api_client,和前面的测试相互隔离 def test_public_api_again(api_client): """🌐 再次测试公共API(验证状态隔离) 输出: [创建API客户端] → (全新实例) [测试4] 再次调用公共API 原始API客户端状态: {'base_url': 'http://127.0.0.1:5000'}(无test_flag) """ print("\n[测试4] 再次调用公共API") print(f" 原始API客户端状态: {api_client}") assert "test_flag" not in api_client # 验证隔离性 ``` --- ## ✅ 总结句话: > **夹具是 pytest 中用来为测试函数提供预置环境和数据的机制,通过函数装饰器 `@pytest.fixture` 定义。你可以像使用参数样在测试中使用它们,pytest 会自动帮你处理依赖关系和执行顺序。** --- ##
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值