1 模块简介
当你开始使用Python编程时,你或许已经使用了iterators(迭代器)和generators(生成器),你当时可能并没有意识到。在本篇博文中,我们将会学习迭代器和生成器是什么。当然,我们也会了解如何创建它们,在我们需要的时候,就可以创建属于我们自己的迭代器和生成器。
2 模块使用
2.1 迭代器
迭代器是一个允许你在一个容器上进行迭代的对象。Python的迭代器主要通过两个方法实现:__iter__和__next__。__iter__要求你的容器支持迭代。它会返回迭代器对象本身。如果你想创建一个迭代器对象,你还需要定义__next__方法,它将会返回容器的下一个元素。
注意: 在Python 2中,命名习惯有所不同,__iter__保持不变,__next__ 改为next。
为了对这些概念更加清晰,让我们回顾下面的两个定义:
- 可迭代对象(iterable),只定义了__iter__方法;
- 迭代器(iterator),定义了__iter__和__next__两个方法,__iter__返回迭代器本身,__next__方法返回下一个元素;
所有函数名中有双下划线的方法,都很神奇,你不需要直接调用__iter__或者__next__。你可以使用for循环或者转换为列表,Python就会自动替你调用这些方法。当然你或许还是想调用它们,那么你可以使用Python内置的函数iter和next方法。
Python 3 提供了一些序列类型,例如list、tuple和range。list是一个可迭代对象,但不是一个迭代器,因为它没有实现__next__方法。我们通过下面的例子,可以很容易发现这点。
>>> my_list = [1,2,3]
>>> next(my_list)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'list' object is not an iterator
当我们在这个例子中,尝试着调用列表的next方法,最终我们接收到一个TypeError异常信息,可以发现列表对象并不是一个迭代器。我们换一个思路,让我们看下面的代码:
>>> iter(my_list)
<list_iterator object at 0x7f6ec6b00f98>
>>> list_iterator = iter(my_list)
>>> next(list_iterator)
1
>>> next(list_iterator)
2
>>> next(list_iterator)
3
>>> next(list_iterator)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
仅仅对列表调用Python内置的iter函数,就可以将列表转换为迭代器。当你对它调用next方法,直到它遍历完所有元素,就会收到StopIteration异常信息。让我们尝试将列表转换为迭代器,然后在循环中遍历它。
>>> for item in iter(my_list):
... print(item)
...
1
2
3
当你使用循环来遍历一个迭代器,你就不需要调用next方法,你也无需担心会收到StopIteration异常信息。
2.2 创建属于你的迭代器
有时候,你也想创建属于你的迭代器。Python很容易地就可以完成这个任务。正如上一章节提到,你需要做的就是在你的类里实现__iter__和__next__这两个方法。让我们创建一个迭代器,这个迭代器可以遍历字符串。
class MyIterator:
def __init__(self,letters):
self.letters = letters
self.position = 0
def __iter__(self):
return self
def __next__(self):
if self.position >= len(self.letters):
raise StopIteration
letter = self.letters[self.position]
self.position += 1
return letter
if __name__ == "__main__":
i = MyIterator('abcd')
for item in i:
print (item)
对于这个例子,在我们的类中,我们仅仅需要实现三个方法。在初始化函数中,我们传入字符串,创建一个类变量用于引用这个字符串。我们也初始化了一个位置变量,以便于我们知道我们在字符串的位置。__iter__方法仅仅返回它本身,这就是是它所要做的。__next__是这个类中最重要的方法,我们首先检查位置是否超出字符串的长度,如果超出,就会抛出StopIteration异常信息。然后,我们提取出字符串上该位置的字符,将位置加1,最后返回该字符。
下面的例子中,我们创建一个无限的迭代器。一个无限的迭代器就是可以一直遍历。你要注意的是当调用这个迭代器时,如果你不加任何限制的话,它们将会导致死循环。
class Doubler():
def __init__(self):
self.number = 0
def __iter__(self):
return self
def __next__(self):
self.number += 1
return self.number * self.number
if __name__ == "__main__":
doubler = Doubler()
count = 0
for number in doubler:
print (number)
if count > 5:
break
count += 1
在这段代码中,我们没有向迭代器中传入任何参数。我们仅仅以此为例进行说明。为了不陷入死循环,我们在开始遍历迭代器之前,加入一个计数器。最后,我们开始遍历,当计时器超过5,循环就会中断。
2.3 生成器
一个普通的Python函数经常返回一个值,无论它是列表、整数还是其他对象。但是如果你想调用一个函数,这个函数能否产生一系列值呢?这个就是生成器诞生的原因。生成器的工作机制是保存它所停止(或者说它已经产生)的位置,然后给主调函数返回一个值。不是返回一个调用函数的执行,生成器仅仅返回一个临时的控制返回。为了完成这个功能,生成器函数需要使用Python的 yield 语句。
注意: 在其它编程语言中,生成器可能叫做协同程序;
让我们先创建一个简单的生成器,
>>> def doubler_generator():
... number = 2
... while True:
... yield number
... number *= number
...
>>> doubler = doubler_generator()
>>> next(doubler)
2
>>> next(doubler)
4
>>> next(doubler)
16
>>> type(doubler)
<class 'generator'>
这个生成器仅仅是创建一个无限的序列。你可以一直对它调用next方法,它绝不会用完所有的值。因为你可以遍历生成器,因此,一个生成器也可以认为是一类迭代器,但是没有人会真正把它们联系在一起。其实,生成器也定义了__next__方法,我们观察上面的例子,这就是为什么next函数可以正常执行。
让我们来看另一个例子,这个例子中只产生3个元素,而不是无限的序列。
>>> def silly_generator():
... yield "Python"
... yield "Rocks"
... yield "So do you!"
...
>>> gen = silly_generator()
>>> next(gen)
'Python'
>>> next(gen)
'Rocks'
>>> next(gen)
'So do you!'
>>> next(gen)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
在这个例子中,我们创建了一个使用yield语句3次的生成器。在每个实例中,它产生不同的字符串。你可以认为yield是生成器的返回语句。无论何时你调用yield,函数都会中止,然后保存它的状态。然后它产生出值,这就是你在上面的例子里通过终端看到的输出。如果我们在函数中有一些变量,那么这些变量也会被保存。
当你看到StopIteration,你应该了解到你已经耗尽了这个迭代器。这就意味着这个迭代器已经用完了。你也可以在上面的迭代器部分看到这个特点。
无论何时我们调用next方法,生成器在它上次离开的地方开始,然后产生下一个值或者我们结束函数,生成器结束。如果你一直不调用next方法,这个状态就会永远保持下去。
让我们实例化这个生成器,然后使用循环来遍历它。
>>> gen = silly_generator()
>>> for item in gen:
... print(item)
...
Python
Rocks
So do you!
我们创建一个新的生成器实例,是因为当我们遍历完这个生成器的时候,我们不希望产生任何事情。这是因为我们已经遍历完这个生成器的实例的所有值。所以在这个例子中,我们创建一个实例,然后通过循环来遍历它,并打印产生出的值。当生成器遍历完,for循环会替我们处理StopIteration异常,然后中断循环。
生成器的一个优点就是它可以遍历很大的数据集,然后一次只返回一部分数据。下面的例子就是当我们打开一个文件,然后逐行返回数据,
with open("temp.txt") as fobj:
for line in fobj:
print line
当我们以这种方式遍历时,Python会将文件对象转换为一个生成器。这种方式允许我们处理空间太大以至于无法读入到内存的文件。对于任意的大数据集,对于这些大数据集,你需要块操作或者当你需要生成一个大数据集但是它将会填满你的内存,你就会发现生成器很有用处。
2.4 总结
到这里,你已经了解了什么是迭代器以及如何使用它。你也了解到可迭代对象和迭代器的区别。最后,我们学习了什么是生成器以及你如何使用它。例如,生成器很适合处理内存有效的数据。