当前位置: 首页 > news >正文

【pytest】finalizer 执行顺序:FILO 原则

文章目录

    • 核心概念:FILO(First-In-Last-Out,先进后出)
    • 第一部分:Yield Fixtures 的执行顺序
      • 代码示例
      • 执行结果
      • 详细执行流程分析
      • 关键理解点
      • 为什么是这个顺序?
    • 第二部分:addfinalizer 的执行顺序
      • 代码示例
      • 执行结果
      • 详细执行流程
      • 栈的可视化
      • 实际应用示例
    • 第三部分:底层实现揭秘
      • Yield Fixture 的底层实现原理
      • 完整执行流程图
      • 为什么要这样设计?
    • 多层嵌套示例
      • 复杂场景
      • 执行结果
      • 调用栈可视化
    • 实际应用建议
      • 1. **利用 FILO 特性设计资源清理**
      • 2. **避免顺序陷阱**
    • 总结

核心概念:FILO(First-In-Last-Out,先进后出)

Finalizers 遵循 栈式执行顺序,类似于:

  • 函数调用栈
  • 资源获取即初始化(RAII)模式
  • 嵌套的 with 语句

原则:最后注册的 finalizer 最先执行


第一部分:Yield Fixtures 的执行顺序

代码示例

def test_bar(fix_w_yield1, fix_w_yield2):print("test_bar")@pytest.fixture
def fix_w_yield1():yieldprint("after_yield_1")@pytest.fixture
def fix_w_yield2():yieldprint("after_yield_2")

执行结果

test_bar
.after_yield_2
after_yield_1

详细执行流程分析

时间轴 →1. Setup 阶段(从左到右)┌─────────────────┐│ fix_w_yield1    │ ← 第一个参数,先执行 setup│   执行到 yield   │└─────────────────┘↓┌─────────────────┐│ fix_w_yield2    │ ← 第二个参数,后执行 setup│   执行到 yield   │└─────────────────┘2. 测试执行┌─────────────────┐│   test_bar()    │ ← 打印 "test_bar"└─────────────────┘3. Teardown 阶段(从右到左,栈式弹出)┌─────────────────┐│ fix_w_yield2    │ ← 最后 setup 的,最先 teardown│ after_yield_2   │    打印 "after_yield_2"└─────────────────┘↓┌─────────────────┐│ fix_w_yield1    │ ← 最先 setup 的,最后 teardown│ after_yield_1   │    打印 "after_yield_1"└─────────────────┘

关键理解点

def test_bar(fix_w_yield1, fix_w_yield2):#    ↑           ↑#   左边        右边(最后一个参数)
  • Setup 顺序:左 → 右(fix_w_yield1fix_w_yield2
  • Teardown 顺序:右 → 左(fix_w_yield2fix_w_yield1

为什么是这个顺序?

这是为了依赖关系的正确性

@pytest.fixture
def database():db = create_db()yield dbdb.close()  # 最后关闭@pytest.fixture
def user(database):  # 依赖 databaseu = database.create_user()yield udatabase.delete_user(u)  # 先删除用户def test_something(database, user):pass

执行顺序:

  1. Setup: databaseuser
  2. Test 运行
  3. Teardown: userdatabase ✅(正确:先删用户,再关数据库)

如果反过来就会出错:

  • ❌ 先关数据库,再删用户 → 失败!

第二部分:addfinalizer 的执行顺序

代码示例

from functools import partial
import pytest@pytest.fixture
def fix_w_finalizers(request):request.addfinalizer(partial(print, "finalizer_2"))  # 第一个注册request.addfinalizer(partial(print, "finalizer_1"))  # 第二个注册def test_bar(fix_w_finalizers):print("test_bar")

执行结果

test_bar
.finalizer_1
finalizer_2

详细执行流程

注册顺序(时间从上到下):
┌────────────────────────────┐
│ request.addfinalizer(f2)   │ ← 第一个注册(进栈)
├────────────────────────────┤
│ request.addfinalizer(f1)   │ ← 第二个注册(进栈)
└────────────────────────────┘执行顺序(FILO,从栈顶弹出):
┌────────────────────────────┐
│ finalizer_1 执行           │ ← 最后注册的,最先执行
├────────────────────────────┤
│ finalizer_2 执行           │ ← 最先注册的,最后执行
└────────────────────────────┘

栈的可视化

         栈结构注册时:                执行时:第二次注册              第一个执行↓                      ↑
┌────────┐            ┌────────┐
│   f1   │  ← 栈顶    │   f1   │ ← 先出栈
├────────┤            ├────────┤
│   f2   │            │   f2   │ ← 后出栈
└────────┘            └────────┘↑                      
第一次注册              第二个执行

实际应用示例

@pytest.fixture
def complex_setup(request):# 步骤1:分配内存memory = allocate_memory()request.addfinalizer(lambda: free_memory(memory))# 步骤2:创建数据库连接(依赖内存)db = create_db_connection(memory)request.addfinalizer(lambda: db.close())# 步骤3:创建用户(依赖数据库)user = db.create_user()request.addfinalizer(lambda: db.delete_user(user))return user

清理顺序(FILO):

  1. ✅ 删除用户(最后注册,最先执行)
  2. ✅ 关闭数据库
  3. ✅ 释放内存(最先注册,最后执行)

这保证了依赖关系的正确性!


第三部分:底层实现揭秘

Yield Fixture 的底层实现原理

# 我们写的代码:
@pytest.fixture
def my_fixture():print("setup")yield "resource"print("teardown")# pytest 内部实际做的事(伪代码):
@pytest.fixture
def my_fixture(request):print("setup")# 创建生成器对象gen = generator_function()resource = next(gen)  # 执行到 yield,获取资源# 注册 finalizerdef resume_generator():try:next(gen)  # 继续执行生成器,运行 yield 后的代码except StopIteration:passrequest.addfinalizer(resume_generator)return resource

完整执行流程图

┌─────────────────────────────────────────────────────┐
│  @pytest.fixture                                    │
│  def my_fixture():                                  │
│      print("setup")        ← 1. 执行到这里          │
│      yield "resource"      ← 2. 暂停,返回资源      │
│      print("teardown")     ← 4. finalizer 触发执行  │
└─────────────────────────────────────────────────────┘↓3. 测试运行完成↓
┌─────────────────────────────────────────────────────┐
│  request.addfinalizer(resume_generator)             │
│      ↓                                              │
│  resume_generator() 被调用                          │
│      ↓                                              │
│  next(gen) → 继续执行 yield 之后的代码              │
└─────────────────────────────────────────────────────┘

为什么要这样设计?

  1. 统一机制:所有清理逻辑都通过 addfinalizer 管理
  2. 顺序保证:利用栈结构自动保证正确的清理顺序
  3. 异常安全:即使测试失败,finalizers 也会执行

多层嵌套示例

复杂场景

@pytest.fixture
def fix_a():print("setup A")yieldprint("teardown A")@pytest.fixture
def fix_b(fix_a):  # 依赖 fix_aprint("setup B")yieldprint("teardown B")@pytest.fixture
def fix_c(fix_b):  # 依赖 fix_bprint("setup C")yieldprint("teardown C")def test_example(fix_c):print("TEST")

执行结果

setup A      ← 最底层依赖,先执行
setup B      ← 中间层
setup C      ← 最上层
TEST         ← 测试运行
teardown C   ← 最上层,先清理(FILO)
teardown B   ← 中间层
teardown A   ← 最底层,最后清理

调用栈可视化

Setup(入栈):        Teardown(出栈):┌─────┐             ┌─────┐│  C  │ ← 最后      │  C  │ ← 最先├─────┤             ├─────┤│  B  │             │  B  │├─────┤             ├─────┤│  A  │ ← 最先      │  A  │ ← 最后└─────┘             └─────┘

实际应用建议

1. 利用 FILO 特性设计资源清理

@pytest.fixture
def app_environment(request):# 按依赖顺序注册config = load_config()request.addfinalizer(lambda: config.cleanup())db = init_database(config)request.addfinalizer(lambda: db.shutdown())cache = init_cache(db)request.addfinalizer(lambda: cache.clear())return {'config': config, 'db': db, 'cache': cache}

清理顺序自动正确:cache → db → config ✅

2. 避免顺序陷阱

# ❌ 错误:顺序会反过来
@pytest.fixture
def bad_example(request):request.addfinalizer(cleanup_step_1)  # 实际最后执行request.addfinalizer(cleanup_step_2)request.addfinalizer(cleanup_step_3)  # 实际最先执行
# ✅ 正确:按想要的执行顺序反向注册
@pytest.fixture
def good_example(request):request.addfinalizer(cleanup_step_3)  # 想最后执行,先注册request.addfinalizer(cleanup_step_2)request.addfinalizer(cleanup_step_1)  # 想最先执行,最后注册

总结

方面说明
核心原则FILO(先进后出,栈式结构)
Yield fixtures最右边的参数最先清理
addfinalizer最后注册的最先执行
底层实现yield 通过 addfinalizer 实现
设计目的保证依赖资源的正确清理顺序
最佳实践按依赖关系顺序创建资源,自动逆序清理

这种设计模式在编程中非常常见,就像俄罗斯套娃:最外层的最先关闭,最内层的最后关闭,确保不会出现"还在用的资源已经被销毁"的问题。

http://www.dtcms.com/a/438437.html

相关文章:

  • Windows11配置MSYS2+vscode+cpp+cmake环境
  • flash网站需要改变足球比赛直播观看
  • 批量M3U8转MP4工具
  • 关于棋牌游戏网站建设文案app 网站开发公司电话
  • 族谱家谱抖音快手微信小程序看广告流量主开源
  • 红河蒙自网站开发网站默认样式
  • 新版本Jenkins(2.516.1)界面如何设置为中文
  • CentOS 7 系统安装步骤(从U盘启动到桌面详细流程)附镜像下载​
  • [xboard] 23 kernel启动流程之汇编篇
  • 教育网站开发需求说明书同步朋友圈到wordpress
  • Lua上值与闭包
  • C语言指针全解析:从内存本质到高级应用的系统化探索
  • 博客网站开发背景及意义上传网站软件
  • 数据链路层协议——以太网,ARP协议
  • stub区域 概念及题目
  • 使用QT Designer建立QT视窗操作面简介
  • 好的外贸网站的特征商务网站建设方案ppt
  • Java代码审计-Servlet基础(1)
  • 微信建网站平台的宁河网站建设
  • 做教育网站有FTP免费网站
  • 【详细】idea设置格式化方式 google style
  • 关于智能体互联协议标准的130天
  • 君正T32开发笔记之IVSP版本环境搭建和编译
  • DDR Study - MR Registers during the Clock Switch
  • Claude Code 的魔力
  • Node.js 常用工具
  • Node.js 的替代品Bun
  • 网站平台建设所需开发工具广安做网站的公司
  • 阿里云做网站送服务器吗显示网站建设中
  • 【AGI使用教程】Meta 开源视觉基础模型 DINOv3(1)下载与使用