如何防止或捕获 yield 调用函数中的 StopIteration 异常?
How can I prevent or trap StopIteration exception in the yield-calling function?
我们的一个库中的生成器返回函数(即其中包含 yield
语句的函数)由于未处理的 StopIteration
异常而未通过某些测试。为方便起见,在 post 中我将此函数称为 buggy
.
我一直没能找到buggy
的方法来防止异常(不影响函数的正常运行)。同样,我还没有找到在 buggy
.
中捕获异常(使用 try
/except
)的方法
(Client code using buggy
可以捕获这个异常,但是这发生得太晚了,因为具有正确处理导致条件的必要信息的代码此异常是 buggy
函数。)
我正在使用的实际代码和测试用例太复杂了 post 这里,所以我创建了一个非常简单,但也 非常人为 的玩具说明问题的示例。
首先,具有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"
,那么只会打印第一行的信息。
当使用空文件和字符串 "first"
作为参数调用 myscript.py
脚本时,我试图捕获的 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
(或者更确切地说,它的现实世界对应物)“过早”到达此流的末尾,可以这么说。
我认为如果我可以测试 stream
是否在其末尾,在 for row in reader:
行之前就足够了,但我也没有想出办法来做到这一点。测试 stream.read(1)
返回的值是 0 还是 1 会告诉我流是否在其末尾,但在后一种情况下 stream
的内部指针将指向一个字节太远 csvfile
的内容。 (此时 stream.seek(-1, 1)
和 stream.tell()
都不起作用。)
最后,对于任何想要 post 这个问题的答案的人:如果您利用我上面提供的示例代码在 [=124] 之前测试您的提案,那将是最有效的=]正在处理它。
编辑: 我试过的 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"
作为参数。在此 post 的演示中引发 StopIteration
异常的特定输入组合并不代表导致我们的代码发出有问题的 StopIteration
异常的真实情况。因此,演示脚本对空文件加上 "first"
字符串组合的“正确响应”(无论可能是什么)与我正在处理的实际问题无关。
你可以用这种方式在 buggy
函数的词法范围内捕获 StopIteration
异常:
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
迭代器和
手动请求第一个值
- 如果成功,则从 csv 文件中读取第一行并将其交给
buggy
函数的调用者
- 如果失败,如空 csv 文件的情况,一些字符串,例如生成
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
的调用者在这种情况下得到的异常是由于 buggy
函数在完成之前没有产生任何值。
mcernak很好的解决和描述了你遇到的问题
但是,这个问题背后存在一个设计问题:调用者 有时 并不期望生成器,而是 non-empty 迭代器
换个角度来看,文件丢失了怎么办?对于来自 open
和 return 的函数句柄 IOError
一些哨兵或将其提升给调用者更有意义吗?
与其试图强迫您的生成器与虐待它的调用者一起工作,不如考虑
- 提供两个函数(一个可以调用另一个)
- 为生成器的最大行数提供参数(可能是最好的)
# mymod.py
import csv
import itertools
def notbuggy(csvfile, max_rows=None):
with open(csvfile) as stream:
yield from itertools.islice(csv.reader(stream), max_rows)
#!/usr/bin/env python3
# myscript.py
import sys
import mymod
def print_row(row):
print(*row, sep='\t')
def main(csvfile, mode=None):
max_rows = 1 if mode == "first" else None
for row in mymod.notbuggy(csvfile, max_rows):
print_row(row)
if __name__ == '__main__':
main(*sys.argv[1:])
使用next()
时,调用逻辑必须同意
之一
- 永远不要在一个空的可迭代对象上调用它(先检查文件?)
- 处理生成器的异常(
StopIteration
,一些自定义 Exception
)
- 处理一些空标记(可能是
""
、一些字符串、None
或 object
..)
然而,调用者做了 none 个,所以保证没有很好地设置!
如果调用者想要不止一行或将空标记解释为一个值怎么办?除非这些在文档中以某种方式传达,否则调用者总是会误用函数并且不知道为什么它会出现意外行为。
>>> next(iter(()))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>> g = iter((1,))
>>> next(g)
1
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>> print_row("STOP SENTINEL")
S T O P S E N T I N E L
我们的一个库中的生成器返回函数(即其中包含 yield
语句的函数)由于未处理的 StopIteration
异常而未通过某些测试。为方便起见,在 post 中我将此函数称为 buggy
.
我一直没能找到buggy
的方法来防止异常(不影响函数的正常运行)。同样,我还没有找到在 buggy
.
try
/except
)的方法
(Client code using buggy
可以捕获这个异常,但是这发生得太晚了,因为具有正确处理导致条件的必要信息的代码此异常是 buggy
函数。)
我正在使用的实际代码和测试用例太复杂了 post 这里,所以我创建了一个非常简单,但也 非常人为 的玩具说明问题的示例。
首先,具有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"
,那么只会打印第一行的信息。
当使用空文件和字符串 "first"
作为参数调用 myscript.py
脚本时,我试图捕获的 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
(或者更确切地说,它的现实世界对应物)“过早”到达此流的末尾,可以这么说。
我认为如果我可以测试 stream
是否在其末尾,在 for row in reader:
行之前就足够了,但我也没有想出办法来做到这一点。测试 stream.read(1)
返回的值是 0 还是 1 会告诉我流是否在其末尾,但在后一种情况下 stream
的内部指针将指向一个字节太远 csvfile
的内容。 (此时 stream.seek(-1, 1)
和 stream.tell()
都不起作用。)
最后,对于任何想要 post 这个问题的答案的人:如果您利用我上面提供的示例代码在 [=124] 之前测试您的提案,那将是最有效的=]正在处理它。
编辑: 我试过的 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"
作为参数。在此 post 的演示中引发 StopIteration
异常的特定输入组合并不代表导致我们的代码发出有问题的 StopIteration
异常的真实情况。因此,演示脚本对空文件加上 "first"
字符串组合的“正确响应”(无论可能是什么)与我正在处理的实际问题无关。
你可以用这种方式在 buggy
函数的词法范围内捕获 StopIteration
异常:
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
迭代器和
- 如果成功,则从 csv 文件中读取第一行并将其交给
buggy
函数的调用者 - 如果失败,如空 csv 文件的情况,一些字符串,例如生成
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
的调用者在这种情况下得到的异常是由于 buggy
函数在完成之前没有产生任何值。
mcernak很好的解决和描述了你遇到的问题
但是,这个问题背后存在一个设计问题:调用者 有时 并不期望生成器,而是 non-empty 迭代器
换个角度来看,文件丢失了怎么办?对于来自 open
和 return 的函数句柄 IOError
一些哨兵或将其提升给调用者更有意义吗?
与其试图强迫您的生成器与虐待它的调用者一起工作,不如考虑
- 提供两个函数(一个可以调用另一个)
- 为生成器的最大行数提供参数(可能是最好的)
# mymod.py
import csv
import itertools
def notbuggy(csvfile, max_rows=None):
with open(csvfile) as stream:
yield from itertools.islice(csv.reader(stream), max_rows)
#!/usr/bin/env python3
# myscript.py
import sys
import mymod
def print_row(row):
print(*row, sep='\t')
def main(csvfile, mode=None):
max_rows = 1 if mode == "first" else None
for row in mymod.notbuggy(csvfile, max_rows):
print_row(row)
if __name__ == '__main__':
main(*sys.argv[1:])
使用next()
时,调用逻辑必须同意
- 永远不要在一个空的可迭代对象上调用它(先检查文件?)
- 处理生成器的异常(
StopIteration
,一些自定义Exception
) - 处理一些空标记(可能是
""
、一些字符串、None
或object
..)
然而,调用者做了 none 个,所以保证没有很好地设置!
如果调用者想要不止一行或将空标记解释为一个值怎么办?除非这些在文档中以某种方式传达,否则调用者总是会误用函数并且不知道为什么它会出现意外行为。
>>> next(iter(()))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>> g = iter((1,))
>>> next(g)
1
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>> print_row("STOP SENTINEL")
S T O P S E N T I N E L