【发布时间】:2021-01-25 13:11:57
【问题描述】:
由于未处理的StopIteration 异常,我们的一个库中的一个生成器返回函数(即其中包含yield 语句的函数)未能通过一些测试。为方便起见,在这篇文章中,我将此函数称为buggy。
我一直没能找到buggy防止异常的方法(不影响函数的正常运行)。同样,我也没有找到在buggy 内捕获异常(带有try/except)的方法。
(客户端代码使用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",那么只会打印第一行的信息。
当myscript.py 脚本被一个空文件和字符串"first" 作为参数调用时,我试图捕获的StopIteration 异常出现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
问:如何在buggy 函数的词法范围内防止或捕获此StopIteration 异常?
重要提示:请记住,在上面给出的示例中,myscript.py 脚本是“客户端代码”的替代,因此不在我们的控制范围内。这意味着任何需要更改 myscript.py 脚本的方法都不能解决实际的现实问题,因此它不是这个问题的可接受答案。
上面显示的简单示例与我们的实际情况之间的一个重要区别是,在我们的示例中,有问题的输入流不是来自空文件。如果buggy(或者更确切地说,它的真实世界对应物)“过早”到达此流的末尾,可以这么说,就会出现问题。
我认为如果我可以在for row in reader: 行之前测试stream 是否在其末尾就足够了,但我也没有想出办法做到这一点。测试stream.read(1) 返回的值是 0 还是 1 将告诉我流是否在其末尾,但在后一种情况下,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" 字符串组合的“正确响应”,无论是什么,都与我正在处理的实际问题无关。
【问题讨论】:
-
我不知道你的真实代码是什么样子的,但这个玩具例子只是说明你的
main函数有问题。buggy函数非常好。 -
如果你想让
buggy做一些 other 非常好的事情,不同于现在正在做的事情,那么说什么,也许我们可以帮忙(或告诉你这是不可能的)。 -
您仍然忽略了您希望
buggy的行为方式的问题。客户端代码要求一些根本不存在的东西。buggy应该做什么? -
编辑中的变化没有意义。您的行为就像客户端从 csv 阅读器获得的 StopIteration 一样,而不是来自
buggy本身的信号,通知客户端没有更多内容了。是 客户端 违反了 Python 中的这一基本迭代契约(不处理此信号),而不是buggy。 -
这绝对看起来像XY problem。试图捕获 StopIteration 似乎不是解决您最初试图解决的问题的正确方法。
标签: python python-3.x csv exception generator