为何在__init__外生成器无法正常迭代?Python迭代器问题解析
关于Python生成器与迭代器类的疑问
在编写迭代器类时,我遇到了一个问题。为深入探究,我编写了两个实现__next__()方法的类(我知道直接迭代类实例需要__iter__(),但为简化问题,选择直接调用__next__())。同时让__next__()返回其取值对象的id,以判断返回值是否来自同一个生成器实例。
测试代码
from typing import Optional class Gen1: def __init__(self) -> None: self.content: generator = self.content() def __next__(self) -> Optional[tuple[int,int]]: try: return next(self.content), id(self.content) except StopIteration: return None def content(self) -> generator[int]: yield 1 yield 2 yield 3 class Gen2: def __next__(self) -> tuple[int,int]: x: generator = self.content() return next(x),id(x) def content(self) -> generator[int]: yield 1 yield 2 yield 3
两类的唯一区别是:Gen1在__init__()中将生成器函数content()赋值给实例属性,而Gen2在__next__()方法内直接调用content()并赋值。
Gen1测试结果
创建Gen1实例并多次调用__next__(),预期返回1、2、3后返回None,且所有值来自同一个生成器对象(__init__()中创建的实例),实际结果符合预期:
gen1 = Gen1() print(f"Class: {gen1.__class__.__name__}, id:{id(gen1)}") for _ in range(0,5): print(next(gen1)) >>> Class: Gen1, id:2912767032672 (1, 2912762151312) (2, 2912762151312) (3, 2912762151312) None None
Gen2测试结果
对Gen2做相同操作,我预期两种结果之一:
__next__()每次生成新的content(),因此始终返回1,但每次id不同;__next__()记住生成器,行为与Gen1一致。
实际结果如下:
gen2 = Gen2() print(f"Class: {gen2.__class__.__name__}, id:{id(gen2)}") for _ in range(0,5): print(next(gen2)) >>> Class: Gen2, id:2912767032096 (1, 2912766724464) (1, 2912766724464) (1, 2912766724464) (1, 2912766724464) (1, 2912766724464)
更奇怪的是创建两个Gen2实例:
gen2 = Gen2() gen3 = Gen2() print(f"Class: {gen2.__class__.__name__}, id:{id(gen2)}") for _ in range(0,5): print(next(gen2)) print('=====================') print(f"Class: {gen3.__class__.__name__}, id:{id(gen3)}") for _ in range(0,5): print(next(gen3)) >>> Class: Gen2, id:1961519922000 (1, 1961519614320) (1, 1961519614320) (1, 1961519614320) (1, 1961519614320) (1, 1961519614320) ===================== Class: Gen3, id:1961519920944 (1, 1961519614320) (1, 1961519614320) (1, 1961519614320) (1, 1961519614320) (1, 1961519614320)
两个不同类实例(id不同)返回的值来自同一个生成器(id相同),且仍不迭代,完全不符合预期。似乎必须将生成器函数保存到实例变量,原因是什么?
问题总结
- 为何同一对象调用
__next__()却无法正常迭代?将content()赋值给__init__()中的实例属性,究竟做了什么让实例记住生成器状态? - 是否可以让
__next__()记住生成器状态,或无需在方法外保存content()返回的生成器对象,就能实现正常迭代?这种需求是否合理?
解答
问题1解答
- 生成器是有状态的迭代器,每次调用生成器函数(比如
content())都会创建一个全新的生成器对象,从头开始执行。 - Gen1在
__init__中仅调用一次content(),把生成器对象存储在self.content实例属性里,后续每次调用__next__都是对同一个生成器对象执行next(),因此能依次获取yield的1、2、3,直到生成器耗尽抛出StopIteration。 - Gen2的
__next__方法里每次都会调用self.content(),相当于每次都新建一个生成器对象,调用next()只会取第一个yield的值1,这个生成器用完后就会被垃圾回收。 - 关于id相同的问题:Python的垃圾回收机制会复用已销毁对象的内存地址,所以每次新建的生成器刚好复用了之前被回收的生成器的id,看起来像是同一个对象,但实际上每次都是全新的生成器实例。
问题2解答
- 可以实现,但必须保存生成器的状态,实例属性是最直接的存储方式,也可以用懒加载的方式延迟生成器的创建:
from typing import Optional class Gen3: def __init__(self) -> None: self._content: Optional[generator] = None def __next__(self) -> Optional[tuple[int,int]]: # 懒加载:首次调用时才创建生成器 if self._content is None: self._content = self.content() try: return next(self._content), id(self._content) except StopIteration: return None def content(self) -> generator[int]: yield 1 yield 2 yield 3
- 需求本身合理,但生成器的核心特性就是维护迭代状态,因此必须有一个存储状态的载体(实例属性是符合Python面向对象设计的选择)。如果不想在
__init__中提前创建生成器,懒加载是可行的替代方案,但本质还是要把生成器对象存在实例变量里。
内容的提问来源于stack exchange,提问作者calvin
相关产品推荐
相关产品推荐

