【发布时间】:2022-12-14 04:36:48
【问题描述】:
我有一个生成器gen,具有以下属性:
- 让它产生收益非常昂贵(比创建生成器更昂贵)
- 元素占用了相当多的内存
- 有时所有的
__next__调用都会抛出异常,但是创建生成器不会告诉你什么时候会发生
我自己没有实现生成器。
有没有办法让生成器产生它的第一个元素(我将在 try/except 中执行此操作),如果我之后循环遍历它,生成器随后不会在第二个元素上启动?
我想创建一些这样的代码:
try:
first = next(gen)
except StopIterator:
return None
except Exception:
print("Generator throws exception on a yield")
# looping also over the first element which we yielded already
for thing in (first, *gen):
do_something_complicated(thing)
我可以看到哪些解决方案不是很好:
- 创建生成器,测试第一个元素,创建一个新的生成器,循环遍历第二个。
- 将整个 for 循环放在 try/except 中;不太好,因为 yield 抛出的异常非常普遍,它可能会捕获其他东西。
- 产生第一个元素,测试它,然后从第一个元素和
gen的其余部分改造一个新的生成器(最好不要将所有gen的元素提取到列表中,因为这可能会占用大量内存).对于 3,这似乎是最好的解决方案,我上面给出的例子就是一个几乎存在的例子,但我相信这只会在我们开始迭代之前将
gen的所有元素提取到一个元组中,我想这样做避免。
【问题讨论】:
-
每个迭代器都应该有一个
gen.__length_hint__()函数,它返回剩余元素的数量。但是你必须小心处理它,因为它是一个提示,可能不包含真实的长度。 -
@areop-enap:并非所有迭代器都有
__length_hint__。特别是,发电机没有。 -
对于选项 3,请参阅
itertools.chain。 -
如何创建一个将生成器作为参数的新类,它可以有一个内部队列,可以帮助促进新的
peek()操作(可以调用传递的生成器上的 next(),然后将值保存在队列中)。如果队列非空,则从队列中弹出,否则直接从生成器中产生。会使发电机稍微贵一点,但考虑到你的发电机已经很贵了应该没问题。 -
我猜这就是你在向 Jay 提出的建议。我的问题实际上源于这样一个事实,即我希望我可以在实际的 for 语句中尝试/除外;我真的需要
for try: thing in gen except Exception: continue之类的东西,这是不可能的。所以我想更好的选择是创建一个新的生成器来包装旧的生成器并执行yield next(gen),并尝试除那里以捕获异常。我处理异常的理想方式是continue循环,所以我想我可以让它产生一个占位符对象来知道我什么时候应该在实际循环中继续。