Python 使用pytest测试文件读取函数时出错
我有一个简单的功能:Python 使用pytest测试文件读取函数时出错,python,python-3.x,pytest,Python,Python 3.x,Pytest,我有一个简单的功能: def read_file(fp): with open(fp) as fr: for line in fr.readlines(): yield line 当我在一个不存在的文件上运行此函数时,我得到: FileNotFoundError: [Errno 2] No such file or directory: 'idontexist.txt' 在另一个文件中,我尝试使用pytest测试此函数: import pyte
def read_file(fp):
with open(fp) as fr:
for line in fr.readlines():
yield line
当我在一个不存在的文件上运行此函数时,我得到:
FileNotFoundError: [Errno 2] No such file or directory: 'idontexist.txt'
在另一个文件中,我尝试使用pytest
测试此函数:
import pytest
from utils import read_file
def test_file_not_exist():
filepath = 'idontexist.txt'
with pytest.raises(FileNotFoundError):
read_file(filepath)
但是,运行pytest
,我得到了以下消息:
E Failed: DID NOT RAISE <class 'FileNotFoundError'>
E失败:未引发
为什么这次考试不及格 您正在创建一个生成器函数。调用generator函数将返回一个generator对象:
>>> def read_file(fp):
... with open(filepath) as fr:
... for line in fr.readlines():
... yield line
...
>>> read_file('asd')
<generator object read_file at 0x10554ee08>
问题在于,如果在打开
和关闭
之间发生某种情况(例如异常或返回
等),文件可能无法关闭。您可以使用try/finally
解决此问题,即
f = open(filename)
try:
f.read(4)
# etc
finally:
f.close()
这很麻烦,因此我们使用with
语句将其缩短为
with open(filename) as f:
f.read(4)
# etc
这很好,因为它减少了混乱,并且在文件关闭之前,无法使用语句离开。但是,当您在上面的read\u文件
这样的生成器中执行此操作时,可能会有人用
for line in read_file(filename):
if line.startswith('#'):
break
现在,在中断后
生成器在产量处暂停
它无法知道它不会再次迭代,因此它在那里等待。with
块中的yield
允许您在不关闭文件的情况下离开上下文管理器。(使用try/finally
时也会出现同样的问题,但在这种情况下可能更为明显。)即使您知道不会break
循环体的异常也会产生同样的效果
在这种情况下,由于CPython中的ref counting GC,文件可能会被关闭:当收集生成器时,它将被关闭,在终止with
块时抛出异常,从而关闭文件。这并不比允许GC直接收集文件对象f
(也通过file.\uu del\uu
关闭文件)好多少
简单的规则是:
不要在带有
语句的中产生
这意味着您通常应该在生成器外部使用with
语句。所以你做了一些类似的事情
def read_file(f):
for line in f.readlines():
yield line
# Control resource at top level
with open(filename) as fin:
for line in read_file(fin) # pass the resource to generator
# do something with line
另一点:迭代器的全部意义在于,它允许我们不必执行诸如将整个文件读入内存之类的操作。因此,与其调用将while文件读入内存的readlines()
,不如直接迭代该文件,该文件一次只读取一行。通过这两项更改,您的函数如下所示:
def read_file(f):
for line in f:
yield line
甚至:
def read_file(f):
yield from f
就迭代器而言,这只是标识函数,因此它是冗余的,可以删除。因此,无论您在哪里使用read_lines
功能,您都可以使用
with open(filename) as fin:
for line in fin:
# do stuff
(即代码中不再有
读取行
功能)这是有意义的。谢谢相关问题:open
是否为生成器流中的每个元素调用一次?No open仅在请求第一个元素时调用。生成器函数的规则意味着当您请求第一个元素时,它将继续执行,直到第一个产生。另外,我不建议在这样的生成器函数中使用和,我会更新答案来解释原因。谢谢,解释得很好!
def read_file(f):
yield from f
with open(filename) as fin:
for line in fin:
# do stuff