为什么在尝试将生成器拆分为生成器时尝试 except 不捕获 StopIteration

问题描述 投票:0回答:1

尝试将大小为 a*n + b 的生成器拆分为 1 个生成器,每个生成器的大小为 n。

我相信这个问题已经存在问题,只需进行最小的修改这里。然而令人困惑的是,下面的代码返回一个错误,尽管据说已经用 try except 捕获了它。

def test(vid, size):
    while True:
        try:
            part = (next(vid) for _ in range(size))
            yield part
        except StopIteration:
            break

res = test((i for i in range(100)), 30)
for i in res:
    for j in i:
        print(i, end=" ")
    print()
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
Cell In[54], line 4, in (.0)
      3 try:
----> 4     part = (next(vid) for _ in range(size))
      5     yield part

StopIteration: 

The above exception was the direct cause of the following exception:

RuntimeError                              Traceback (most recent call last)
Cell In[54], line 11
      9 res = test((i for i in range(100)), 30)
     10 for i in res:
---> 11     for j in i:
     12         print(i, end=" ")
     13     print()

RuntimeError: generator raised StopIteration

此外,打印语句中的剩余部分也被打印出来,而不是被丢弃。

注意:使用

RuntimeError
表示 except 也不会捕获错误

python generator
1个回答
0
投票

有几件事需要解决:

  • 首先请注意,您的函数会产生一个生成器,但它不会从中产生 empty / 产生。简而言之,
    try...except
    一定是在迭代发生的地方,目前它在定义周围。

让我给你这个类比,以使其(希望如此)显而易见:

foo
不会在test2
函数内部被调用 -> 因此
无法在内部捕获错误。

def test2(): try: def foo(): raise StopIteration return foo except StopIteration #this will not happen as foo() is not called here. pass outer_foo = test2() outer_foo() # <--- raises StopIteration


其次,让我们回到生成器并使用您的函数

test

。正如你所说,发生了
RuntimeError

try: for geni in test(): for elem in geni: print(elem) except StopIteration: print("This is expected but does NOT HAPPEN. Why?") except RuntimeError: print("Instead a RuntimeError occurs.")
那么,为什么这段代码没有捕获 

StopIteration

 而是产生 
RuntimeError

原因在于你如何定义生成器以及生成器在 python 中如何工作。生成器定义为:


part = (next(vid) for _ in range(size)) # <--- StopIteration CANNOT be caught


Python 中的生成器有自己的作用域 来自 StopIteration

 的预期 
next(vid)
 在该作用域中引发 
但未在生成器的作用域中处理,它将 作为 RuntimeError 传播到外部

你的发电机相当于。

for _ in range(size): yield next(vid) # <--- here a StopIteration can be caught
此版本没有“隐藏”范围,并且行为符合预期,您只需要注意,如果发生 

vid

 的迭代,则必须再次捕获错误。

这是一个可行的解决方案:

def create_batches(vid, size): done = False def batcher(): nonlocal done print("--- new batch ---") for i in range(size): print("batch", i, "/", size) try: yield next(vid) except StopIteration: print("StopIteration caught, and we are done") done=True break while not done: yield batcher() for batch in create_batches((i for i in range(10)), 3): for elem in batch: print("elem=", elem)
# --- Output --- #

--- new batch ---
batch 0 / 3
elem= 0
batch 1 / 3
elem= 1
batch 2 / 3
elem= 2
--- new batch ---
batch 0 / 3
elem= 3
batch 1 / 3
elem= 4
batch 2 / 3
elem= 5
--- new batch ---
batch 0 / 3
elem= 6
batch 1 / 3
elem= 7
batch 2 / 3
elem= 8
--- new batch ---
batch 0 / 3
elem= 9
batch 1 / 3
StopIteration caught, and we are done


最后说明:您应该能够使用

itertools.isclice

 创建更紧凑的批处理程序,但为了更好地理解,我没有将其包含在此处。

© www.soinside.com 2019 - 2024. All rights reserved.