为什么通过pytest调用生成器时不抛出StopIteration异常?
考虑这个生成器:
def sample():
print("Setup")
yield
print("Teardown")
gen = sample()
next(gen)
next(gen)
当我第一次调用 next(gen) 时,生成器会执行到 print("Setup") 这一步,然后下一次我再调用它时,它会继续执行到 print("Teardown") 之后。不过,因为没有第二个 yield 语句,所以会抛出 StopIteration 异常。但是当我做了以下修改:
import pytest
@pytest.fixture
def sample():
print("Setup")
yield
print("Teardown")
def test_case(sample):
print("Executing test case")
现在,当我运行 pytest 命令时,所有的代码都能正常执行,首先是设置部分,然后是测试案例,最后是拆卸部分。我确定 pytest 在执行过程中调用了生成器两次,因为所有的打印语句都被执行了。但奇怪的是,为什么这里没有抛出 StopIteration 异常呢?我猜测是 pytest 在内部处理了这个问题。如果我猜错了,请纠正我。谢谢。
2 个回答
看看@ pytest.fixture的源代码,可以在fixtures.py找到。
@pytest.fixture
是一个装饰器,这意味着它会创建一个新的函数,这个新函数看起来和sample()
一模一样,但它外面包裹了一层东西。第一次调用sample()
时,这个包裹会期待这个函数正常返回结果。第二次调用sample()
时,它就期待这个函数返回StopIteration
,并且会把这个返回值给丢掉。
一般来说,你不能指望一个有装饰器的函数和没有装饰器的同一个函数表现得一样。
next()
是一个比较底层的功能,而 StopIteration
也是底层的东西。这些都是迭代协议的一部分。另一方也必须遵守这些规则,如果出现 StopIteration
,必须要处理它(否则会变成 RuntimeError
- PEP479)。
StopIteration
这个异常总是在生成器的末尾被抛出,如果你没有看到错误,说明调用者必须直接(用 try-except
)或者间接地处理了这个异常,比如:
gen = sample()
# the for statement speaks the iteration protocol
# behind the scenes
for v in gen:
print(f"got {v}")