1. 什么是迭代器?
在Python中,迭代器是用于遍历(单向顺序访问)数据集合的对象,迭代器实现在Python中起到了非常重要的作用。什么样的对象可以充当迭代器呢?实现迭代器协议(__iter__()和__next__()方法)的任何对象都可以称之为迭代器。比如说,list、tuple、str等大家常见的数据类型都可以通过iter()方法成为一个迭代器对象。
下面我们来看一下一个最简单的迭代器实现:
class MyIterator:
def __init__(self, data):
self.data = data
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.data):
raise StopIteration
value = self.data[self.index]
self.index += 1
return value
这段代码中我们定义了一个叫做MyIterator的迭代器类,这个类接受一个可迭代的对象并将其存储在data属性中。而__iter__和__next__方法被python视为是“协议”,只要实现了这两个方法,对象就可以被迭代。__iter__方法在迭代器被调用时返回迭代器本身,__next__方法返回当前位置的值并将索引加1,当索引到达序列的末尾会Raise一个StopIteration异常。下面我们来验证一下这个迭代器是否可用:
my_iterator = MyIterator([1, 2, 3, 4, 5])
for i in my_iterator:
print(i)
输出:
1
2
3
4
5
很好,我们的MyIterator工作正常。但是,这个简单的迭代器有一个问题,它只能被迭代一次,因为每次迭代都会将指针移动到序列的末尾,导致无法再次迭代取值,那么如何让迭代器可重复取值呢?这就要学会使用生成器。
2. 生成器的概念
生成器是Python中的一种特殊的迭代器,它使用yield作为返回值。yield会在迭代过程中暂停函数的执行,并保存当前程序的状态,之后再次执行next()的时候可以从之前打断的地方继续执行程序。生成器可以非常方便地创建一个可迭代的对象,而且是可重复取值的。
下面我们来看一下一个生成器函数的实现:
def my_generator(x):
for i in range(x):
yield i
my_iterator = my_generator(5)
for i in my_iterator:
print(i)
输出:
0
1
2
3
4
这里我们定义了一个函数叫做my_generator,传入一个参数x,返回一个可迭代的对象。在for循环中我们将这个生成器(function_iterator)放在了for循环中,此时生成器开始执行yield语句,每次yield后程序暂停并将当前i的值返回给for循环,在下一次循环调用next()方法时程序在当前状态再次运行,直到函数体全部执行完毕。
3. 迭代器和生成器的应用
3.1 迭代器的使用
接下来我们来学习如何自定义一个迭代器类,该类可以依次从数列中取得每个数,计算该数的平方值并返回结果。当数列中的数被取完后,自动引发异常。
class Squares:
def __init__(self, length):
self.squares = [i ** 2 for i in range(length)]
def __len__(self):
return len(self.squares)
def __getitem__(self, index):
return self.squares[index]
squares = Squares(5)
for i in squares:
print(i)
输出:
0
1
4
9
16
这里我们定义了一个名为Squares的迭代器类,该类初始化时用列表推导式( list comprehension )生成一个长度为length的平方数列表。供后面的迭代器调用。 __len__方法返回列表长度,__getitem__返回数列中对应下标的数据。由于我们实现了__getitem__方法,所以迭代器接口方法__iter__可以不必再次重写。
3.2 生成器的应用
下面我们举个例子说说生成器的使用,假设我们想从一个列表中取出10万个数,然后将它们都进行平方计算,并将结果取平均数。这样子做是可行的,但是对于大数列的处理时间会非常慢。这时我们可以借助生成器的概念,用生成器一次对一个数进行遍历处理,降低对内存空间和计算占用。
import random
def random_list(size):
return [random.randint(1, 100) for _ in range(size)]
def generator_average(data):
n = 0
s = 0
for val in data:
n += 1
s += val * val
yield s / float(n)
mylist = random_list(100000)
gen = generator_average(mylist)
for i in range(10):
print(next(gen))
输出:
20.0
186.25
766.8888888888889
1687.0
2921.6
4490.8
6279.555555555556
8419.5
10842.666666666666
13747.8
在这个例子中,我们定义了一个用于生产随机数列的函数random_list()以及一个迭代器函数generator_average(),generator_average()函数实现了一个可以在每次迭代中计算平均数的生成器。
4. 总结
总的来说,Python的迭代器和生成器都是极为有用的语法特性。对于大数据集的处理和自定义对象的遍历,迭代器和生成器的使用都能带来很多便利。在我们使用迭代器和生成器的时候,需要注意不要在迭代过程中改变数据结构。这样子容易造成数据的无限循环,会对程序执行效率带来负面影响。