文章目录
核心概念: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():
yield
print("after_yield_1")
@pytest.fixture
def fix_w_yield2():
yield
print("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_yield1→fix_w_yield2) - Teardown 顺序:右 → 左(
fix_w_yield2→fix_w_yield1) 
为什么是这个顺序?
这是为了依赖关系的正确性:
@pytest.fixture
def database():
db = create_db()
yield db
db.close()  # 最后关闭
@pytest.fixture
def user(database):  # 依赖 database
u = database.create_user()
yield u
database.delete_user(u)  # 先删除用户
def test_something(database, user):
pass
执行顺序:
- Setup: 
database→user - Test 运行
 - Teardown: 
user→database✅(正确:先删用户,再关数据库) 
如果反过来就会出错:
- ❌ 先关数据库,再删用户 → 失败!
 
第二部分: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):
- ✅ 删除用户(最后注册,最先执行)
 - ✅ 关闭数据库
 - ✅ 释放内存(最先注册,最后执行)
 
这保证了依赖关系的正确性!
第三部分:底层实现揭秘
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,获取资源
# 注册 finalizer
def resume_generator():
try:
next(gen)  # 继续执行生成器,运行 yield 后的代码
except StopIteration:
pass
request.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 之后的代码              │
└─────────────────────────────────────────────────────┘
为什么要这样设计?
- 统一机制:所有清理逻辑都通过 
addfinalizer管理 - 顺序保证:利用栈结构自动保证正确的清理顺序
 - 异常安全:即使测试失败,finalizers 也会执行
 
多层嵌套示例
复杂场景
@pytest.fixture
def fix_a():
print("setup A")
yield
print("teardown A")
@pytest.fixture
def fix_b(fix_a):  # 依赖 fix_a
print("setup B")
yield
print("teardown B")
@pytest.fixture
def fix_c(fix_b):  # 依赖 fix_b
print("setup C")
yield
print("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 实现 | 
| 设计目的 | 保证依赖资源的正确清理顺序 | 
| 最佳实践 | 按依赖关系顺序创建资源,自动逆序清理 | 
这种设计模式在编程中非常常见,就像俄罗斯套娃:最外层的最先关闭,最内层的最后关闭,确保不会出现"还在用的资源已经被销毁"的问题。
                    
                
                
            
        
浙公网安备 33010602011771号