我们的一个库中的生成器返回函数(即其中包含yield
语句的函数)由于未处理的StopIteration
异常而导致某些测试失败。为了方便起见,在本文中,我将此功能称为buggy
。
我还没有找到buggy
防止异常的方法(不影响函数的正常运行)。同样,我也没有找到在中捕获异常(带有try
/ except
)的方法buggy
。
(使用的客户端代码buggy
可以捕获此异常,但这为时已晚,因为该buggy
函数具有正确处理导致此异常的条件所必需的信息。)
我正在使用的实际代码和测试用例过于复杂,无法在此处发布,因此我创建了一个非常简单但又非常人为的玩具示例来说明问题。
一,具有以下buggy
功能的模块:
# mymod.py
import csv # essential!
def buggy(csvfile):
with open(csvfile) as stream:
reader = csv.reader(stream)
# how to test *here* if either stream is at its end?
for row in reader:
yield row
如评论所述,使用csv
模块(来自Python 3.x标准库)是此问题的基本特征1。
该示例的下一个文件是一个脚本,旨在代替“客户端代码”。换句话说,此脚本超出此示例的“实际目的”在很大程度上是无关紧要的。它在示例中的作用是提供一种简单,可靠的方法来引发该buggy
功能的问题。(例如,某些代码可以重新用于测试套件中的测试用例。)
#!/usr/bin/env python3
# myscript.py
import sys
import mymod
def print_row(row):
print(*row, sep='\t')
def main(csvfile, mode=None):
if mode == 'first':
print_row(next(mymod.buggy(csvfile)))
else:
for row in mymod.buggy(csvfile):
print_row(row)
if __name__ == '__main__':
main(*sys.argv[1:])
该脚本将CSV文件的路径作为必需参数和可选的第二个参数。如果省略第二个参数,或者它不是字符串"first"
,则脚本将打印为stdout
CSV文件中的信息,但格式为TSV。如果第二个参数是字符串"first"
,则仅打印第一行中的信息。
StopIteration
我尝试捕获的异常是在myscript.py
使用空文件并将字符串"first"
作为参数2调用脚本时发生的。
这是此代码的示例:
% cat ok_input.csv
1,2,3
4,5,6
7,8,9
% ./myscript.py ok_input.csv
1 2 3
4 5 6
7 8 9
% ./myscript.py ok_input.csv first
1 2 3
% cat empty_input.csv
# no output (of course)
% ./myscript.py empty_input.csv
# no output (as desired)
% ./myscript.py empty_input.csv first
Traceback (most recent call last):
File "./myscript.py", line 19, in <module>
main(*sys.argv[1:])
File "./myscript.py", line 13, in main
print_row(next(mymod.buggy(csvfile)))
StopIteration
问:如何StopIteration
在buggy
函数的词法范围内防止或捕获此异常?
重要提示:请记住,在上面给出的示例中,myscript.py
脚本是“客户端代码”的替代,因此不在我们的控制范围内。这意味着,任何需要更改myscript.py
脚本的方法都无法解决实际的实际问题,因此,对于该问题而言,这不是可接受的答案。
上面显示的简单示例与我们的实际情况之间的一个重要区别是,在我们的情况下,有问题的输入流不是来自空文件。可以说,问题发生在buggy
(或者,与其实际情况相对应)“太早”到达此流的末尾的情况。
我认为如果我可以测试任一行是否stream
在终点之前就足够了for row in reader:
,但是我也没有找到一种执行此操作的方法。测试by返回的值stream.read(1)
是0还是1会告诉我stream是否在其末尾,但是在后一种情况下stream
,的内部指针将指向指向其csvfile
内容的一个字节。(既不stream.seek(-1, 1)
也不stream.tell()
在这一点上的工作。)
最后,对于任何想发布此问题答案的人:如果您要利用我上面提供的示例代码在发布提案之前对其进行测试,那将是最有效的。
编辑:mymod.py
我尝试过的一种变化是:
import csv # essential!
def buggy(csvfile):
with open(csvfile) as stream:
reader = csv.reader(stream)
try:
firstrow = next(reader)
except StopIteration:
firstrow = None
if firstrow != None:
yield firstrow
for row in reader:
yield row
此变体失败,并显示与原始版本几乎相同的错误消息。
当我第一次阅读@mcernak的建议时,我认为它与上述变体非常相似,因此希望它也会失败。然后我惊喜地发现事实并非如此!因此,截至目前,有一个确定的候选人可以获得赏金。就是说,我很想了解为什么上面的变体在@mcernak成功的同时却无法捕获异常。
1我要处理的实际情况是遗留代码;从csv
短期来看,从模块切换到其他方法不是我们的选择。
2请完全忽略以下问题:演示脚本以空文件和字符串"first"
作为参数调用时,其“正确响应应为” 。StopIteration
在这篇文章的演示中,引发异常的特定输入组合并不代表导致我们的代码发出有问题的StopIteration
异常的真实情况。因此,演示脚本对空文件加"first"
字符串组合的“正确响应”(无论可能是什么)与我要处理的现实问题无关。
您可以通过以下方式将StopIteration
异常捕获在buggy
函数的词法范围内:
import csv # essential!
def buggy(csvfile):
with open(csvfile) as stream:
reader = csv.reader(stream)
try:
yield next(reader)
except StopIteration:
yield 'dummy value'
for row in reader:
yield row
您基本上是从reader
迭代器手动请求第一个值,然后
buggy
函数的调用者dummy value
则会产生一些字符串,例如,以防止buggy
函数的调用程序崩溃之后,如果csv文件不为空,则将在for循环中读取(并产生)剩余的行。
编辑:为了说明为什么mymod.py
问题中提到的其他变体不起作用,我向其中添加了一些打印语句:
import csv # essential!
def buggy(csvfile):
with open(csvfile) as stream:
reader = csv.reader(stream)
try:
print('reading first row')
firstrow = next(reader)
except StopIteration:
print('no first row exists')
firstrow = None
if firstrow != None:
print('yielding first row: ' + firstrow)
yield firstrow
for row in reader:
print('yielding next row: ' + row)
yield row
print('exiting function open')
运行它会得到以下输出:
% ./myscript.py empty_input.csv first
reading first row
no first row exists
exiting function open
Traceback (most recent call last):
File "myscript.py", line 15, in <module>
main(*sys.argv[1:])
File "myscript.py", line 9, in main
print_row(next(mymod.buggy(csvfile)))
这表明,如果输入文件为空,则第一个try..except
块可以正确处理StopIteration
异常,并且该buggy
功能可以正常继续。在这种情况下
,buggy
gets的调用者的例外是由于该buggy
函数在完成之前不产生任何值这一事实。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句