如何防止或捕获yield 调用函数中的StopIteration 异常?

我们的库之一中的生成器返回函数(即其中包含yield语句的函数)由于未处理的StopIteration异常而导致某些测试失败。为了方便起见,在这篇文章中我将此函数称为buggy。


我一直无法找到一种方法来buggy防止异常(不影响函数的正常运行)。同样,我还没有找到一种方法来捕获异常(使用try/ except)buggy。


(客户端代码usingbuggy可以捕获此异常,但这发生得太晚了,因为具有正确处理导致此异常的条件所需信息的代码是函数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",则脚本将以TSVstdout格式打印到CSV 文件中的信息。如果第二个参数是字符串,则仅打印第一行中的信息。"first"


当使用空文件和字符串作为参数2StopIteration调用脚本时,会出现我试图捕获的异常。myscript.py"first"


MMMHUHU
浏览 122回答 2
2回答

MMTTMM

mcernak很好地解决并描述了您遇到的问题然而,这个问题背后存在一个设计问题:调用者有时并不期望生成器,而是非空迭代器从另一个角度来看,如果文件丢失了怎么办?对于函数句柄并返回一些哨兵或将其提升给调用者是否更有IOError意义open?不要试图强制你的生成器与虐待它的调用者一起工作,而是考虑提供两个函数(一个可以调用另一个)为生成器的最大行数提供一个参数(可能是最好的)# mymod.pyimport csvimport itertoolsdef notbuggy(csvfile, max_rows=None):&nbsp; &nbsp; with open(csvfile) as stream:&nbsp; &nbsp; &nbsp; &nbsp; yield from itertools.islice(csv.reader(stream), max_rows)#!/usr/bin/env python3# myscript.pyimport sysimport mymoddef print_row(row):&nbsp; &nbsp; print(*row, sep='\t')def main(csvfile, mode=None):&nbsp; &nbsp; max_rows = 1 if mode == "first" else None&nbsp; &nbsp; for row in mymod.notbuggy(csvfile, max_rows):&nbsp; &nbsp; &nbsp; &nbsp; print_row(row)if __name__ == '__main__':&nbsp; &nbsp; main(*sys.argv[1:])使用时next(),调用逻辑必须同意以下之一永远不要在空的可迭代对象上调用它(先检查文件?)处理来自生成器的异常(StopIteration一些自定义Exception)处理一些空的哨兵(也许""是一些字符串,None或object..)然而,调用者没有执行这些操作,因此保证没有很好地设置!如果调用者想要多个行或将空哨兵解释为值怎么办?除非这些在文档中以某种方式传达,否则调用者总是可以误用函数并且不知道为什么它会出现意外的行为。>>> next(iter(()))Traceback (most recent call last):&nbsp; File "<stdin>", line 1, in <module>StopIteration>>> g = iter((1,))>>> next(g)1>>> next(g)Traceback (most recent call last):&nbsp; File "<stdin>", line 1, in <module>StopIteration>>> print_row("STOP SENTINEL")S&nbsp; &nbsp;T&nbsp; &nbsp;O&nbsp; &nbsp;P&nbsp; &nbsp; &nbsp; &nbsp;S&nbsp; &nbsp;E&nbsp; &nbsp;N&nbsp; &nbsp;T&nbsp; &nbsp;I&nbsp; &nbsp;N&nbsp; &nbsp;E&nbsp; &nbsp;L

喵喵时光机

StopIteration您可以通过以下方式在函数的词法范围内捕获异常buggy:import csv&nbsp; # essential!def buggy(csvfile):&nbsp; &nbsp; with open(csvfile) as stream:&nbsp; &nbsp; &nbsp; &nbsp; reader = csv.reader(stream)&nbsp; &nbsp; &nbsp; &nbsp; try:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; yield next(reader)&nbsp; &nbsp; &nbsp; &nbsp; except StopIteration:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; yield 'dummy value'&nbsp; &nbsp; &nbsp; &nbsp; for row in reader:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; yield row您基本上手动从迭代器请求第一个值reader,然后如果成功,将从 csv 文件中读取第一行并将其提供给buggy函数的调用者如果失败,就像空 csv 文件的情况一样,dummy value会产生一些字符串,以防止函数的调用者buggy崩溃之后,如果 csv 文件不为空,则将在 for 循环中读取(并生成)剩余的行。编辑:为了说明为什么问题中提到的其他变体mymod.py不起作用,我添加了一些打印语句:import csv&nbsp; # essential!def buggy(csvfile):&nbsp; &nbsp; with open(csvfile) as stream:&nbsp; &nbsp; &nbsp; &nbsp; reader = csv.reader(stream)&nbsp; &nbsp; &nbsp; &nbsp; try:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; print('reading first row')&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; firstrow = next(reader)&nbsp; &nbsp; &nbsp; &nbsp; except StopIteration:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; print('no first row exists')&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; firstrow = None&nbsp; &nbsp; &nbsp; &nbsp; if firstrow != None:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; print('yielding first row: ' + firstrow)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; yield firstrow&nbsp; &nbsp; &nbsp; &nbsp; for row in reader:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; print('yielding next row: ' + row)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; yield row&nbsp; &nbsp; &nbsp; &nbsp; print('exiting function open')运行它会给出以下输出:% ./myscript.py empty_input.csv firstreading first rowno first row existsexiting function openTraceback (most recent call last):&nbsp; File "myscript.py", line 15, in <module>&nbsp; &nbsp; main(*sys.argv[1:])&nbsp; File "myscript.py", line 9, in main&nbsp; &nbsp; print_row(next(mymod.buggy(csvfile)))这表明,如果输入文件为空,第一个try..except块会正确处理StopIteration异常,并且buggy函数会正常继续。在这种情况下,调用者得到的异常buggy是由于该buggy函数在完成之前不会产生任何值。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Python