为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

MMMHUHU 2023-10-26 10:45:08
我们的库之一中的生成器返回函数(即其中包含yield语句的函数)由于未处理的StopIteration异常而导致某些测试失败。为了方便起见,在这篇文章中我将此函数称为buggy。我一直无法找到一种方法来buggy防止异常(不影响函数的正常运行)。同样,我还没有找到一种方法来捕获异常(使用try/ except)buggy。(客户端代码usingbuggy可以捕获此异常,但这发生得太晚了,因为具有正确处理导致此异常的条件所需信息的代码是函数buggy。)我正在使用的实际代码和测试用例太复杂,无法在此处发布,因此我创建了一个非常简单但又极其人为的玩具示例来说明问题。一、模块功能buggy:# mymod.pyimport 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.pyimport sysimport mymoddef 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"
查看完整描述

2 回答

?
MMTTMM

TA贡献1869条经验 获得超4个赞

mcernak很好地解决并描述了您遇到的问题


然而,这个问题背后存在一个设计问题:调用者有时并不期望生成器,而是非空迭代器


从另一个角度来看,如果文件丢失了怎么办?对于函数句柄并返回一些哨兵或将其提升给调用者是否更有IOError意义open?


不要试图强制你的生成器与虐待它的调用者一起工作,而是考虑


提供两个函数(一个可以调用另一个)

为生成器的最大行数提供一个参数(可能是最好的)

# 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..)

然而,调用者没有执行这些操作,因此保证没有很好地设置!


如果调用者想要多个行或将空哨兵解释为值怎么办?除非这些在文档中以某种方式传达,否则调用者总是可以误用函数并且不知道为什么它会出现意外的行为。


>>> 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


查看完整回答
反对 回复 2023-10-26
?
喵喵时光机

TA贡献1846条经验 获得超7个赞

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,然后

  • 如果成功,将从 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函数在完成之前不会产生任何值。


查看完整回答
反对 回复 2023-10-26
  • 2 回答
  • 0 关注
  • 80 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信