如何用Generator类模拟yield from?最优实现方案探讨
由于需要在生成器内部使用self引用,我采用Generator类而非生成器函数,但希望实现类似yield from的功能。示例生成器函数如下:
def gen1(): yield "foo" def gen2(): t = gen1() yield from t yield "bla" for i in gen2(): print(i)
我编写了如下Generator类实现,请问这是模拟yield from的最优方式吗?
from collections.abc import Generator class Test(Generator): def __init__(self): self.count = 5; def send(self, *args): print(f"Test.send({args})") self.count -= 1 if self.count == 0: raise StopIteration() return "foo" def throw(self, value): print(f"Test.throw({value})") class Test2(Generator): def __init__(self): self.count = 5; self.test = Test() def send(self, *args): if self.test: try: return self.test.send(*args) except StopIteration: self.test = None print(f"Test2.send({args})") self.count -= 1 if self.count == 0: raise StopIteration() return "foo" def throw(self, value): print(f"Test2.throw({value})") test2 = Test2() while True: res = test2.send("bla") print(f"res = {res}")
注:关于继承(含多层继承)的问题已解决,仅需确认上述实现是否为最优方案。
你的实现核心逻辑是正确的,但算不上最优,还有几个关键细节可以优化,让它更贴近原生yield from的行为:
转发
throw方法到子生成器:
当前Test2的throw方法直接处理异常,没有转发给内部的test子生成器,而原生yield from会优先将异常转发给子生成器处理,只有子生成器抛出StopIteration时才会回到父生成器。修改后的throw方法如下:def throw(self, value): if self.test: try: return self.test.throw(value) except StopIteration: self.test = None print(f"Test2.throw({value})") raise value实现
close方法:Generator抽象基类定义了close方法,用于清理生成器资源,原生yield from会调用子生成器的close方法,你的实现需要补充这部分逻辑:def close(self): if self.test: try: self.test.close() finally: self.test = None # 这里可以添加父生成器自身的清理逻辑处理子生成器的返回值:
原生yield from会捕获子生成器抛出的StopIteration并提取其value属性作为返回值,当前实现没有处理这部分,如果你的场景需要用到子生成器的返回值,需要在捕获StopIteration时保存这个值:def send(self, *args): if self.test: try: return self.test.send(*args) except StopIteration as e: self.test = None self._subgen_return_value = e.value # 保存子生成器返回值 # 后续逻辑...代码复用与可读性:
如果有多个类需要实现类似的yield from逻辑,可以抽取出通用的混合类或者工具函数,避免重复编写转发逻辑;另外用明确的状态变量(比如self._subgen_active)替代self.test = None,能提升代码的可读性。
补充这些细节后,你的实现就能更完整、健壮地模拟原生yield from的行为,同时保持代码的可维护性。
内容的提问来源于stack exchange,提问作者Goswin von Brederlow

