我有一个简单的功能:
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.13/angular.min.js"></script>
<div ng-app="app" ng-controller="parentCtrl">
<b>Parent Event:</b> {{message}}<br />
<br/><br/>
<div ng-controller="childCtrl">
<b>Child Event:</b> {{data.name}}<br />
<input type="button" ng-click="update()" value ="update"/>
</div>
</div>
当我在不存在的文件上运行此函数时,我得到:
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
为什么这个测试没有通过?
答案 0 :(得分:3)
您正在创建生成器功能。调用生成器函数会返回一个生成器对象:
>>> 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>
在遍历生成器之前,不会调用open调用(应该引发FileNotFoundError)。然后你会看到
>>> g = read_file('asd')
>>> for x in g:
... pass
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in read_file
NameError: name 'filepath' is not defined
我猜你在发布之前已将fp
改为filepath
。您可以解决这个问题,但无论哪种方式,您都不会看到来自read_file
的任何错误,除非您通过迭代来使用返回的生成器。
编辑:我不建议在像这样的生成器中使用with
语句。原因是无法保证文件将被关闭。
要理解这一点,请考虑with
的目的是什么。在上下文管理器之前,您将打开一个像
f = open(filename)
f.read(4)
# etc
f.close()
这样做的问题是,如果return
和open
之间发生了某些事情(例如异常或close
等),该文件可能无法关闭。您可以使用try/finally
即
f = open(filename)
try:
f.read(4)
# etc
finally:
f.close()
这很麻烦,所以我们有with
语句将其缩短为
with open(filename) as f:
f.read(4)
# etc
这很好,因为它可以减少混乱,并且在没有关闭文件的情况下无法离开with
语句。但是,当你在read_file
以上的生成器中执行此操作时,有人可能会使用
for line in read_file(filename):
if line.startswith('#'):
break
现在break
生成器暂停在yield
后,它无法知道它不会再次迭代,所以它会在那里等待。 yield
块中的with
允许您离开上下文管理器而不关闭文件。 (使用try/finally
时也会出现同样的问题,但在这种情况下可能会更明显。)即使你知道你从循环体中获得了break
异常会产生同样的效果。
在这种情况下会发生的事情是,由于CPython中的ref计数GC,文件将可能关闭:当收集生成器时,它将被关闭,抛出异常终止with
块,从而关闭文件。这并不比允许GC直接收集文件对象f
(也通过file.__del__
关闭文件)好得多。
简单的规则是:
在
中不要yield
声明with
这意味着您通常应该在生成器外部使用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
另一点:迭代器的重点在于它们允许我们不必执行诸如将整个文件读入内存之类的操作。因此,不是调用readlines()
将while文件读入内存,而是应该直接迭代文件,一次只读取一行。通过这两项更改,您的功能如下:
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
(即代码中没有read_lines
函数)