生成器 Generators
要理解生成器,首先要理解迭代器,迭代器由以下三个部分组成:
- 可迭代对象(iterable)
- 迭代器(iterator)
- 迭代(iteration)
1. 可迭代对象
只要定义了可以返回一个迭代器的__iter__方法的对象,或者定义了可以支持下标索引的__getitem__方法。
2. 迭代器
只要定义了一个__next__方法的对象。
3. 迭代
从某个地⽅(⽐如⼀个列表)取出⼀个元素的过程。当我们使⽤⼀个循环来遍历某个东西时,这就叫⼀个迭代。
生成器
生成器也是一种迭代器,但是只能对其迭代一次。因为生成器没有把所有的值存在内存中,而是在运行时生成值(lazy)。可以通过遍历来使用它们,使用for循环或传递给任意可以进行迭代的函数和结构。
大多数生成器是以函数来实现的,它们并不返回一个值,而是yield一个值。
生成器最好的使用场景是:不需要一次将所有计算的结果全部放在内存中,或者一次读出全部的原始数据,这样会造成很大的内存占用。
上述例子中,我们用for循环来迭代这个生成器函数,同样,我们可以使用python的next()函数来获得迭代器的下一个元素。
基础数据类型list和str都是可迭代对象,我们经常会使用for循环来获得每一个下标的数据。
但是我们发现,不能调用next来迭代字符串和数据,这是因为:
数组和字符串是可迭代对象,但不是迭代器。
使用for关键字迭代可迭代对象时,for会自动获取到迭代器,进行迭代。但是next只能对迭代器进行迭代,因此我们需要使用到iter()函数来根据一个可迭代对象返回一个迭代器对象!
同时,next方法没有实现对StopIteration异常的捕捉,在自己写的迭代器内一般需要额外进行迭代结束的编写。
测验:
现在我们要写一个生成器,迭代一个数组的时候,不再是按顺序取出而是隔一个元素取出。
1. 函数式迭代器(yield)
def skip_elements(arr):
for i in range(0, len(arr), 2): # 从索引0开始,每次跨越2个元素
yield arr[i]
my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9]
gen = skip_elements(my_list)
for element in gen:
print(element)
2. 编写一个迭代器:
class SkipElementsGenerator:
def __init__(self, arr):
self.arr = arr
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.arr):
raise StopIteration
element = self.arr[self.index]
self.index += 2 # 每次跳过一个元素
return element
my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9]
gen = SkipElementsGenerator(my_list)
for element in gen:
print(element)
SkipElementsGenerator
是一个迭代器,我们直接将要迭代的数组当成形参丢入以初始化迭代器。
注意:因为要用for迭代迭代器,因此迭代器也必须得是可迭代对象,这里只需要简单实现__iter__
函数并且返回self
即可!
Another Way
比较直观了解可迭代对象与迭代器的例子:
class MyArray:
def __init__(self, arr):
self.arr = arr
def __iter__(self):
return SkipElementsGenerator(self.arr)
class SkipElementsGenerator:
def __init__(self, arr):
self.arr = arr
self.index = 0
def __next__(self):
if self.index >= len(self.arr):
raise StopIteration
element = self.arr[self.index]
self.index += 2 # 每次跳过一个元素
return element
# 创建自定义数组对象
my_list = MyArray([1, 2, 3, 4, 5, 6, 7, 8, 9])
# 使用生成器迭代元素
for element in my_list:
print(element)
原始数据是可迭代对象,实现__iter__
方法,返回它的迭代器。
迭代器实现__next__
方法,迭代返回每一次迭代的值。
这个时候for循环直接迭代可迭代对象,不像上一个例子一样是迭代迭代器。
for循环是先找到可迭代对象的迭代器! 通过__iter__方法!
然后对迭代器进行迭代! 通过迭代器的__next__方法!
这样写的话可以实现函数的解耦,不会像上面代码一样,还要实例化迭代器gen = SkipElementsGenerator(my_list)
,然后人为迭代迭代器。
这样写的话,只需要像原来一个,实例化自定义的数据类型,直接用for或者iter来获得迭代器,代码中不会出现迭代器的名字,无感使用。