You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何在__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相同),且仍不迭代,完全不符合预期。似乎必须将生成器函数保存到实例变量,原因是什么?

问题总结

  1. 为何同一对象调用__next__()却无法正常迭代?将content()赋值给__init__()中的实例属性,究竟做了什么让实例记住生成器状态?
  2. 是否可以让__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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 18:58:06