为什么未捕获 Python 生成器中的异常?
Why are exceptions within a Python generator not caught?
我有以下实验代码,其功能类似于zip
内置。它试图做的事情应该简单明了,尝试一次 return 压缩元组,直到我们停止生成器时发生 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']))
此行从未 returns 并且似乎使机器挂起。现在怎么样了?
尝试用以下内容替换 yield (arg[i] for ...)
。
for arg in args:
yield arg[i]
但是 导致异常的数字 1[1]
是没有意义的。我建议只用 arg
替换 arg[i]
。
yield
每次都会生成一个生成器对象,并且在创建生成器时完全没有问题。这就是 my_zip
中的 try...except
没有捕捉到任何东西的原因。第三次执行时,
list(arg[2] for arg in args)
这就是它如何简化为(对于我们的理解而言过于简化)现在,仔细观察,list
正在迭代生成器,而不是实际的 my_zip
生成器。现在,list
在生成器对象上调用 next
并对 arg[2]
求值,结果发现 2
不是 arg
的有效索引(这是 [1, 2]
在这种情况下),所以 IndexError
被引发,并且 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>]
所以代码试图构建生成器对象的无限列表。
抱歉,对于未能捕获到异常,我无法提供连贯的解释,但是,有一种简单的解决方法;在最短序列的长度上使用 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
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']))
我有以下实验代码,其功能类似于zip
内置。它试图做的事情应该简单明了,尝试一次 return 压缩元组,直到我们停止生成器时发生 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']))
此行从未 returns 并且似乎使机器挂起。现在怎么样了?
尝试用以下内容替换 yield (arg[i] for ...)
。
for arg in args:
yield arg[i]
但是 导致异常的数字 1[1]
是没有意义的。我建议只用 arg
替换 arg[i]
。
yield
每次都会生成一个生成器对象,并且在创建生成器时完全没有问题。这就是 my_zip
中的 try...except
没有捕捉到任何东西的原因。第三次执行时,
list(arg[2] for arg in args)
这就是它如何简化为(对于我们的理解而言过于简化)现在,仔细观察,list
正在迭代生成器,而不是实际的 my_zip
生成器。现在,list
在生成器对象上调用 next
并对 arg[2]
求值,结果发现 2
不是 arg
的有效索引(这是 [1, 2]
在这种情况下),所以 IndexError
被引发,并且 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>]
所以代码试图构建生成器对象的无限列表。
抱歉,对于未能捕获到异常,我无法提供连贯的解释,但是,有一种简单的解决方法;在最短序列的长度上使用 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
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']))