(生成器)yield与(迭代器)generator
一、问题起源:
def yield_test():a = iter(range(100))cnt = 1while True:cnt += 1if cnt > 100:breakprint('s')yield 1print('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 = startself.stop = stopself.step = stepdef __iter__(self):return selfdef __next__(self):if (self.step > 0 and self.current >= self.stop) or (self.step < 0 and self.current <= self.stop):raise StopIterationelse:value = self.currentself.current += self.stepreturn 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) # 获取下一个元素# 循环体代码,使用 itemexcept 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 特别适合于创建可以生成大量值或无限序列的函数,因为它允许函数输出一个值后暂停执行,不需要一次性生成并保存所有值。