【问题标题】:Why are exceptions within a Python generator not caught?为什么 Python 生成器中的异常没有被捕获?
【发布时间】:2015-05-08 03:06:07
【问题描述】:

我有以下实验代码,其功能类似于内置的zip。它试图做的事情应该简单明了,尝试一次返回一个压缩元组,直到我们停止生成器时出现IndexError

def my_zip(*args):
    i = 0
    while True:
        try:
            yield (arg[i] for arg in args)
        except IndexError:
            raise StopIteration
        i += 1

但是,当我尝试执行以下代码时,IndexError 没有被捕获,而是被生成器抛出:

gen = my_zip([1,2], ['a','b'])
print(list(next(gen)))
print(list(next(gen)))
print(list(next(gen)))


IndexError                                Traceback (most recent call last)
I:\Software\WinPython-32bit-3.4.2.4\python-3.4.2\my\temp2.py in <module>()
     12 print(list(next(gen)))
     13 print(list(next(gen)))
---> 14 print(list(next(gen)))

I:\Software\WinPython-32bit-3.4.2.4\python-3.4.2\my\temp2.py in <genexpr>(.0)
      3     while True:
      4         try:
----> 5             yield (arg[i] for arg in args)
      6         except IndexError:
      7             raise StopIteration
IndexError: list index out of range

为什么会这样?

编辑:

感谢@thefourtheye 为上面发生的事情提供了一个很好的解释。现在执行时又出现了一个问题:

list(my_zip([1,2], ['a','b']))

这条线永远不会回来,似乎挂了机器。现在发生了什么?

【问题讨论】:

  • 粗略地说,如果我正确理解yield 的工作原理,您可以尝试执行def func(): try: return None except: pass 之类的操作。
  • @Riliam,但您提供的代码会发现返回 1 / 0 之类的错误。
  • 与您的问题无关,但您应该 return 而不是提高 StopIteration - 在生成器 is deprecated and will change in the future 内显式提高 StopIteration。

标签: python python-3.x generator


【解决方案1】:
def my_zip(*args):
    i = 0
    while True:
        try:
            yield (arg[i] for arg in args)
        except IndexError:
            raise StopIteration
        i += 1

IndexError 没有被捕获,因为(arg[i] for arg in args) 是一个不会立即执行的生成器,而是在您开始对其进行迭代时执行。当你调用list((arg[i] for arg in args))时,你在另一个范围内迭代它:

# get the generator which yields another generator on each iteration
gen = my_zip([1,2], ['a','b'])
# get the second generator `(arg[i] for arg in args)` from the first one
# then iterate over it: list((arg[i] for arg in args))
print(list(next(gen)))
  • 在第一个list(next(gen)) i 等于0。
  • 第二个list(next(gen)) i 等于1。
  • 在第三个list(next(gen)) i 等于2。在这里你得到IndexError -- 在外部范围内。该行被视为list(arg[2] for arg in ([1,2], ['a','b']))

【讨论】:

    【解决方案2】:

    yield 每次都会生成一个生成器对象,并且在创建生成器时完全没有问题。这就是为什么try...except 中的my_zip 没有捕捉到任何东西的原因。第三次执行的时候,

    list(arg[2] for arg in args)
    

    这就是它被简化为(为了我们的理解而过度简化)的方式,现在,请仔细观察,list 正在迭代生成器,而不是实际的 my_zip 生成器。现在,list 在生成器对象上调用next 并评估arg[2],却发现2 不是arg 的有效索引(在这种情况下为[1, 2]),所以@987654333 @ 被提出,list 无法处理它(无论如何它没有理由处理它),所以它失败了。


    根据编辑,

    list(my_zip([1,2], ['a','b']))
    

    将像这样进行评估。首先,my_zip 将被调用,这将为您提供一个生成器对象。然后使用list 对其进行迭代。它在其上调用next,并获得另一个生成器对象list(arg[0] for arg in args)。由于没有遇到异常或return,它将调用next,以获取另一个生成器对象list(arg[1] for arg in args),并继续迭代。请记住,生成的生成器永远不会迭代,所以我们永远不会得到IndexError。这就是代码无限运行的原因。

    你可以这样确认,

    from itertools import islice
    from pprint import pprint
    pprint(list(islice(my_zip([1, 2], ["a", 'b']), 10)))
    

    你会得到

    [<generator object <genexpr> at 0x7f4d0a709678>,
     <generator object <genexpr> at 0x7f4d0a7096c0>,
     <generator object <genexpr> at 0x7f4d0a7099d8>,
     <generator object <genexpr> at 0x7f4d0a709990>,
     <generator object <genexpr> at 0x7f4d0a7095a0>,
     <generator object <genexpr> at 0x7f4d0a709510>,
     <generator object <genexpr> at 0x7f4d0a7095e8>,
     <generator object <genexpr> at 0x7f4d0a71c708>,
     <generator object <genexpr> at 0x7f4d0a71c750>,
     <generator object <genexpr> at 0x7f4d0a71c798>]
    

    因此代码尝试构建一个无限的生成器对象列表。

    【讨论】:

    • 谢谢! @thefourtheye 的精彩解释。你能看看我对另一个问题的编辑吗?
    • @VictorYan 我现在解决了这个问题。请检查更新的答案。
    • 太棒了!谢谢!我现在对生成器的工作原理有了更好的了解。
    【解决方案3】:

    抱歉,我无法就未能捕获异常提供一个连贯的解释,但是,有一个简单的方法可以解决它;在最短序列的长度上使用 for 循环:

    def my_zip(*args):
        for i in range(min(len(arg) for arg in args)):
            yield (arg[i] for arg in args)
    
    >>> gen = my_zip([1,2], ["a",'b','c'])
    >>> print(list(next(gen)))
    [1, 'a']
    >>> print(list(next(gen)))
    [2, 'b']
    >>> print(list(next(gen)))
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration
    

    【讨论】:

      【解决方案4】:

      尝试将yield (arg[i] for ...) 替换为以下内容。

      for arg in args:
          yield arg[i]
      

      但是如果数字导致1[1] 异常,则没有任何意义。我建议将arg[i] 替换为arg

      【讨论】:

      • 我正在尝试重建内置的 zip 功能,而您的代码没有做应该做的事情。更重要的是,我想了解为什么我的代码不能按应有的方式工作。 (我没有对你投反对票。)
      猜你喜欢
      • 2017-06-18
      • 1970-01-01
      • 2010-11-16
      • 2012-01-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多