使用生成器表达式会导致Python挂起

Joh*_*ohn 6 python list-comprehension generator

我正在试验2个模拟zipPython 2.x和3.x中内置函数的函数.第一个返回一个列表(如在Python 2.x中),第二个是生成器函数,它一次返回一个结果集(如在Python 3.x中):

def myzip_2x(*seqs):
    its = [iter(seq) for seq in seqs]
    res = []
    while True:
        try:
            res.append(tuple([next(it) for it in its]))   # Or use generator expression?
            # res.append(tuple(next(it) for it in its))
        except StopIteration:
            break
    return res

def myzip_3x(*seqs):
    its = [iter(seq) for seq in seqs]
    while True:
        try:
            yield tuple([next(it) for it in its])         # Or use generator expression?
            # yield tuple(next(it) for it in its)
        except StopIteration:
            return

print(myzip_2x('abc', 'xyz123'))                   
print(list(myzip_3x([1, 2, 3, 4, 5], [7, 8, 9])))
Run Code Online (Sandbox Code Playgroud)

这很好用,并提供zip内置的预期输出:

[('a', 'x'), ('b', 'y'), ('c', 'z')]
[(1, 7), (2, 8), (3, 9)]
Run Code Online (Sandbox Code Playgroud)

然后我考虑tuple()用它的(几乎)等效的生成器表达式替换调用中的列表推导,删除方括号[](为什么当生成器应该对期望的迭代是正常的时,使用理解创建一个临时列表tuple(),对吧?)

但是,这会导致Python挂起.如果执行未使用Ctrl C(在Windows上的IDLE中)终止,它将在几分钟后最终停止并出现(预期)MemoryError异常.

调试代码(例如使用PyScripter)显示,在StopIteration使用生成器表达式时,永远不会引发异常.第一个示例呼叫以上至myzip_2x()不断增加空元组res,而第二实例包调用myzip_3x()产量元组(1, 7),(2, 8),(3, 9),(4,),(5,),(),(),(),....

我错过了什么吗?

最后一点:如果在每个函数的第一行中its成为生成器(使用its = (iter(seq) for seq in seqs))(在tuple()调用中使用列表推导时),则会出现相同的挂起行为.

编辑:

谢谢@Blckknght的解释,你是对的.此消息提供了有关使用上述生成器函数的类似示例所发生情况的更多详细信息.总之,用生成器表达式,像这样仅在Python 3.5+工作,并要求from __future__ import generator_stop在文件的顶部声明并改变StopIterationRuntimeError以上(再次使用生成器表达式,而不是列表解析时).

编辑2:

至于上面的最后一点:如果its成为一个生成器(使用its = (iter(seq) for seq in seqs)),它将只支持一次迭代 - 因为生成器是一次性迭代器.因此,第一次运行while循环时它会耗尽,而在后续循环中只会获得空元组.

Blc*_*ght 2

您看到的行为是一个错误。它源于这样一个事实:StopIteration从生成器中冒出的异常与正常退出的生成器无法区分。这意味着您不能使用tryand包装生成器上的循环except并寻找StopIteration将您从循环中打破的方法,因为循环逻辑将消耗异常。

PEP 479提出了解决该问题的方法,即更改语言,使StopIteration生成器内未捕获的内容变成RuntimeError冒泡之前的内容。这将使您的代码能够工作(对您捕获的异常类型进行一些小调整)。

PEP 已在 Python 3.5 中实现,但为了保持向后兼容性,仅当您通过将其放在from __future__ import generator_stop文件顶部来请求时,更改的行为才可用。新行为将在 Python 3.7 中默认启用(Python 3.6 将默认启用旧行为,但如果出现这种情况可能会发出警告)。