(生成器)yield与(迭代器)generator

本文探讨了Python中yield关键字的使用、生成器的工作原理以及与迭代器的区别。重点分析了yield_test函数中的无限循环问题,解释了为何外部while循环导致不断创建新的生成器对象,以及如何通过try-except结构修正。同时介绍了range对象的迭代机制和for循环的迭代原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、问题起源:

def yield_test():
    a = iter(range(100))
    cnt = 1
    while True:
        cnt += 1
        if cnt > 100:
            break
        print('s')
        yield 1
        print('e')

输出无限循环

while True:
    print(next(yield_test()))

二、为什么:

外部的 while True 循环将会导致无限循环,但不是因为 yield_test() 函数内部的逻辑,而是因为外部循环每次迭代都重新创建了一个新的生成器对象。

  • yield_test() 被调用,返回一个新的生成器对象。
  • next(yield_test()) 调用新生成器的 next() 方法。
  • 生成器开始执行,直到遇到第一个 yield 语句,打印 ‘s’ 并产出值 1。
  • 生成器在 yield 处暂停执行,等待下一次 next() 调用以继续执行。
  • 因为外部循环没有保存生成器对象的引用,所以内部生成器的状态丢失了。
  • 外部循环重新开始,再次调用 yield_test() 创建一个新的生成器对象。
  • 步骤 2-6 重复进行,因此 ‘s’ 和 1 被无限打印出来,但 ‘e’ 永远不会被打印

可以忽略掉yield_test内部执行逻辑,从外部调用来看,while会一直执行yield_test方法,永远都不会有终止的时候。

三、修正方法:

a = yield_test()
while True:
	try:
	    print(next(a))
    except StopIteration:
    	# 当生成器耗尽,会抛出 StopIteration 异常
     	break

why: iter(range(100))生成迭代器的本质是什么

Python 中,range 类型代表一个不变的数字序列,并且是惰性计算的,意味着它在需要时才计算每个值,而不是一开始就创建一个数字列表。range 对象可以通过 range(start, stop[, step]) 构造,其中 start 是序列的开始值,stop 是序列结束的边界值,而 step 是两个值之间的差距。

range 对象的 iter 方法负责返回一个迭代器,该迭代器会生成序列中的所有值。这个方法的实现是专门针对 range 对象优化的,并且是在 Python 的 C 语言层面实现的(如果你使用的是 CPython,即 Python 的一个官方实现)。这意味着 range 的 iter 方法是非常高效的,它不会创建一个包含所有值的列表,而是返回一个能够按需计算每个值的迭代器。

当你对 range 对象调用 iter() 函数时,或者在 for 循环中使用 range 对象时,iter 方法就会被调用。然而,因为这部分代码是在 Python 的底层实现中,我们通常看不到其具体的 Python 代码实现。但逻辑上,它大致等同于以下行为:

lass RangeIterator:
    def __init__(self, start, stop, step):
        self.current = start
        self.stop = stop
        self.step = step

    def __iter__(self):
        return self

    def __next__(self):
        if (self.step > 0 and self.current >= self.stop) or (self.step < 0 and self.current <= self.stop):
            raise StopIteration
        else:
            value = self.current
            self.current += self.step
            return value

why: for循环迭代器时,为什么能自动遍历下一个元素呢

在 Python 中,for 循环是一种遍历迭代器的简便方法。它在内部自动处理迭代器的创建和迭代过程,包括调用迭代器的 next() 方法和处理 StopIteration 异常。这是 Python 语言的一个特性,旨在让迭代操作更加直观和易于使用。

当你使用 for 循环遍历一个可迭代对象时,Python 会执行以下步骤:

  • 调用可迭代对象的 iter() 方法来获取一个迭代器对象。
  • 在循环的每次迭代中,调用迭代器的 next() 方法来获取下一个元素。
  • 如果 next() 方法抛出 StopIteration 异常,这意味着迭代器已经没有更多元素可以遍历,for 循环会捕获这个异常并结束循环。
  • 在抛出 StopIteration 之前返回的每个元素都会被赋值给循环变量,并执行循环体。
    下面是一个简化的伪代码,展示了 for 循环背后的逻辑:
iterator = iter(iterable)  # 获取迭代器
while True:
    try:
        item = next(iterator)  # 获取下一个元素
        # 循环体代码,使用 item
    except StopIteration:
        # 迭代器中没有更多元素
        break

why: 生成器和迭代器有什么区别

迭代器(Iterator)和生成器(Generator)是 Python 中实现迭代的两种对象,它们都遵循迭代器协议,但在使用和构建上有所不同。

迭代器(Iterator)

迭代器是一个遵循迭代器协议的对象,这意味着它支持两个方法:iter() 和 next()。iter() 方法返回迭代器对象本身,next() 方法返回序列中的下一个元素。如果所有元素都已经被返回,next() 应该抛出一个 StopIteration 异常。

迭代器可以从可迭代对象(例如列表、元组、集合、字典以及任何实现了 iter() 或 getitem() 方法的对象)中创建,通过调用 iter() 方法实现。

迭代器的关键特点是它们懒惰地计算值,也就是说,它们在请求下一个值之前不会计算该值,这允许它们表示非常大的或无限的序列。

生成器(Generator)

生成器是 Python 中构建迭代器的一种简单而强大的工具。任何包含 yield 表达式的函数都被称为生成器函数。当生成器函数被调用时,它返回一个生成器对象,而不是立即执行函数。

生成器对象本身也是迭代器,它支持上述的迭代器协议。当 next() 方法被调用时(通常是在 for 循环中),生成器函数将从上一次 yield 表达式暂停的地方开始执行,直到遇到下一个 yield 表达式。当生成器函数执行完成而没有遇到新的 yield 时,它将抛出 StopIteration 异常,表明迭代结束。

生成器的一个关键优势是它们可以非常节省内存,因为在任何给定时间都只生成序列中的一个元素。

区别总结
实现方式:迭代器通常由实现了迭代器协议的类的实例组成,而生成器则是由包含 yield 关键字的函数创建。
创建方法:迭代器是显式的,通常通过实现 iter() 和 next() 方法的类来创建。生成器是隐式的,更简单,仅需一个包含 yield 语句的函数。
状态保存:对于迭代器,状态的保存需要自己手动维护。而生成器函数在每次生成一个值后,状态会自动保存,当再次调用 next() 时,从上次返回的地方继续执行。
使用场景:生成器适合于简单的迭代场景,特别是当迭代逻辑可以通过单个函数表达时。迭代器适合于更复杂的情况,需要完全控制迭代逻辑时。

why:yield和return的本质区别是什么

yield 和 return 在 Python 中都用于从函数返回值,但它们在本质上有几个关键的区别:

函数类型

return: 使用 return 的函数是普通的函数。当一个函数执行到 return 语句时,它会返回指定的值,并且函数的执行完全结束。
yield: 包含 yield 语句的函数变成了一个生成器函数。这种函数当执行到 yield 语句时,会返回一个值,但函数的状态会被暂停并保存,以便后续从停止的地方继续执行。

执行流程

return: 只能在函数中出现一次(如果出现在循环或条件语句中,则可能执行多次),一旦执行,就标志着函数的结束。
yield: 可以出现多次,每次遇到 yield,函数会输出一个值并暂停执行,直到下一次通过迭代请求值时再继续执行。

返回值

return: 返回一个单一的值,这个值可以是任何数据类型(包括 None、单个值或者一个数据结构)。
yield: 每次返回迭代中的下一个值,对于生成器函数来说,每次 yield 会返回一个值给迭代器,而整个函数返回的是一个生成器对象。

内存消耗

return: 如果返回的是一个大型数据结构,如大列表,会占用相应的内存空间。
yield: 生成器一次只生成并返回一个值,因此不会一次性占用大量内存。这使得生成器特别适合大数据集或无限序列的情况。

用例

return: 当你需要立即获取一个完整的结果集时。
yield: 当你需要延迟计算结果,或者在计算大型或无限系列的数据集时以减少内存占用。

总的来说,yield 提供了一种生成值的惰性方法,使得函数可以在需要时才生成下一个值,而 return 提供了一种立即返回值的方法。这两种方法在不同的场景下各有优势。

why:yield的实现原理是什么

生成器函数

当你定义一个包含 yield 语句的函数时,这个函数成为生成器函数。与普通函数不同的是,当你调用生成器函数时,它并不立即执行,而是返回一个生成器对象。这个对象遵循迭代器协议,这意味着它实现了 iter() 和 next() 方法。

状态保存

生成器对象在内部保存了生成器函数的执行状态。这包括函数的局部变量、指令指针(即函数中的当前执行位置)和任何待处理的异常状态。这允许生成器函数在每次 yield 被调用时暂停其执行,并在下一次请求值时从上次离开的地方继续执行。

执行流程

当生成器的 next() 方法首次调用时,生成器函数开始执行,直到遇到第一个 yield 表达式。在这个时刻,函数暂停执行,并将 yield 后面的表达式的值返回给调用者。生成器函数的本地变量和执行状态被保存下来,等待下一次 next() 的调用。

当生成器的 next() 方法再次被调用时,生成器函数从上次 yield 暂停的地方恢复执行,直到遇到下一个 yield,或者函数执行结束。如果函数执行结束(即没有遇到新的 yield),生成器对象会抛出 StopIteration 异常,表明迭代已经完成。

内部实现机制

在更底层的实现上,Python 在编译包含 yield 的函数时,会将这个函数标记为生成器函数,并且为这个函数创建一个生成器对象。生成器对象包含一个执行帧(execution frame),用于保存执行状态。yield 本质上是产生一个值并暂停函数执行帧的指令。Python 运行时会处理这些执行帧的创建、保存和恢复。

应用

这种机制使得 yield 特别适合于创建可以生成大量值或无限序列的函数,因为它允许函数输出一个值后暂停执行,不需要一次性生成并保存所有值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值