迭代器与生成器

迭代器

基础概念

在 Python 中,可迭代对象(Iterable)是指可以逐个返回元素的对象,比如列表、元组、字典、集合等。它们都实现了 __iter__() 方法。

迭代器(Iterator)是实现了两个方法的对象:

__iter__():返回迭代器对象本身,通常该方法不做任何操作,直接返回 self。 __next__():返回集合中的下一个元素。如果没有更多元素,抛出 StopIteration 异常。 迭代器的核心在于 __next__() 方法,它让我们可以一遍一遍地遍历可迭代对象。

迭代器的特性

  • 迭代器是一个可以记住遍历位置的对象。

  • 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不能后退。

  • 迭代器是Python中的容器类的数据类型,可以同时存储多个数据,取迭代器中的数据只能一个一个地取,而且取出来的数据在迭代器中就不存在了。

内置迭代器

Python 中的许多内置对象是可迭代的,包括:

  • 列表:[1, 2, 3]
  • 元组:(1, 2, 3)
  • 字符串:“hello”
  • 字典:{‘a’: 1, ‘b’: 2}(可以迭代字典的键、值或键值对)
  • 集合:{1, 2, 3}

我们可以通过 iter() 函数将这些可迭代对象转化为迭代器,然后使用 next() 获取元素。

list=[1,2,3,4] it = iter(list) # 将列表转化为迭代器对象 print (next(it)) # 使用 next() 访问元素 >>> 1 print (next(it)) >>> 2

如果没有更多元素,调用 next() 会抛出 StopIteration 异常:

# 遍历直到 StopIteration 异常 try: while True: print(next(iterator)) except StopIteration: print("迭代结束") sys.exit()

使用 for 循环

实际上,Python 中的 for 循环会自动处理迭代器。我们可以直接用 for 循环遍历任何可迭代对象,无需显式使用 next():

list=[1,2,3,4] it = iter(list) # 转化为迭代器对象 for x in it: print (x)

自定义迭代器

迭代器是一个实现了迭代协议的对象。迭代协议意味着迭代器必须拥有__iter____next__方法。当一个对象实现了这两个方法,我们就称它为迭代器。

__iter__方法返回迭代器自身,而__next__方法返回序列中的下一个值。

如果序列已经到了末尾,则抛出StopIteration异常。

创建一个返回数字的迭代器,初始值为 0,逐步递增 1,迭代次数 limit:

import sys class MyIterator(object): # 1. 初始化属性 def __init__(self, limit): self.limit = limit # 表示迭代次数 self.current = 0 # 表示当前的元素. # 2. 返回迭代器对象本身. def __iter__(self): return self # self = 迭代器对象. # 3. 自定义代码, 表示: 迭代器的规则, 即: 获取下一个元素. def __next__(self): # 3.1 判断, 数据不合法, 就停止迭代. if self.current >= self.limit: # raise: 抛出异常, 并终止程序运行的. raise StopIteration # 抛出异常, StopIteration: 迭代结束. # raise StopIteration('达到了迭代限制。') # 3.2 获取下个元素, 并返回即可. self.current += 1 return self.current if __name__ == '__main__': # 1. 创建迭代器对象. my_iter = MyIterator(5) # 底层调用 iter魔法方法, 返回本身(迭代器对象本身) print(type(my_iter)) # <class '__main__.MyIterator'> # 2. 从迭代器中获取元素. print(f'get item {next(my_iter)}') # 底层调用 next魔法方法, 返回下一个元素 # 遍历. for i in my_iter: # 底层调用 next魔法方法, 返回下一个元素 print(f'for item {i}') # 遍历完后, 迭代器已经没有元素了, 继续迭代会报错. try: print(next(my_iter)) # 底层调用 next魔法方法, 返回下一个元素 except StopIteration as e: print(e) sys.exit()

StopIteration

StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况,在 __next__() 方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代。

if self.current >= self.limit: # raise: 抛出异常, 并终止程序运行的. raise StopIteration # 抛出异常, StopIteration: 迭代结束. # 默认抛出的 StopIteration 不携带信息,如果想携带,使用如下示例: # raise StopIteration('达到了迭代限制。')

生成器

生成器是Python中的一种特殊类型的迭代器,它通常由包含yield语句的函数来定义。与常规函数不同,生成器不会一次性返回所有结果,而是返回一个迭代器对象。

当在生成器函数中使用 yield 语句时,函数的执行将会暂停,并将 yield 后面的表达式作为当前迭代的值返回。

然后,每次调用生成器的 next() 方法或使用 for 循环进行迭代时,函数会从上次暂停的地方继续执行,直到再次遇到 yield 语句。这样,生成器函数可以逐步产生值,而不需要一次性计算并返回所有结果。

调用一个生成器函数,返回的是一个迭代器对象。

yield 关键字

使用 yield 是生成器常用的写法,yield 会记录每个生成的数据, 然后逐个的放到生成器对象中, 最终返回生成器对象

yield 关键字实际上在幕后管理了迭代的状态,它使得生成器函数能够在每次返回时暂停执行,直到下一次 next() 被调用时继续。

def get_generator(): for i in range(1, 6): yield i my_generator = get_generator() # 遍历生成器, 获取每个元素. for i in my_generator: print(i)

生成器推导式

回顾之前的列表推导式,集合推导式

# 生成 1 ~ 5 的数据. my_list = [i for i in range(1, 6)] print(my_list, type(my_list)) # [1, 2, 3, 4, 5] <class 'list'> my_set = {i for i in range(1, 6)} print(my_set, type(my_set)) # {1, 2, 3, 4, 5} <class 'set'>

尝试写一下, “元组”推导式, 发现打印的结果不是元组, 而是对象, 因为这种写法就是生成器推导式

# 生成器推导式 my_tuple = (i for i in range(1, 6)) print(my_tuple) # <generator object <genexpr> at 0x0000024C90F056D0> 生成器对象 print(type(my_tuple)) # <class 'generator'> 生成器类型 # 从生成器中获取数据. # 格式1: for循环遍历 获取全部 for i in my_tuple: print(i) # 格式 2: next()函数, 逐个获取. print(next(my_tuple)) # 1 print(next(my_tuple)) # 2

区别与联系

区别

  • 内存效率:生成器比迭代器更加内存友好。迭代器可能会一次性加载所有数据到内存中,而生成器则是按需生成数据。
  • 使用场景:迭代器通常用于遍历已存在的集合,而生成器则用于生成可能无限大的序列,或者在需要时才计算值的情况。
  • 生命周期:生成器只能被遍历一次,因为它们在每次迭代之后会保留状态,一旦到达序列的末尾就会抛出StopIteration异常并关闭自身。而迭代器理论上可以被多次遍历,尽管实际上许多迭代器设计为只遍历一次。
  • 创建方式:迭代器可以通过实现特定接口来创建,而生成器通常是通过带有yield语句的函数来创建。

联系

  • 都是迭代协议的实现:无论是迭代器还是生成器,它们都遵循迭代协议,即实现了__iter____next__方法。
  • 都可以被迭代:由于生成器本身就是迭代器的一种,所以它们都可以使用for循环或其他迭代机制来访问。
  • 都支持懒加载:两者都支持懒加载模式,即在需要时才生成数据,这对于处理大数据集尤其有用。

内置的迭代器函数

Python 提供了一些常用的迭代器函数,使得迭代更方便:

  • iter():将可迭代对象转化为迭代器。
  • next():返回迭代器的下一个元素。
  • zip():将多个可迭代对象“打包”成一个迭代器。
  • enumerate():返回一个包含索引和元素的迭代器。
  • map() 和 filter():对可迭代对象进行函数操作。