可迭代对象
如果给定一个list
或tuple
,我们可以通过for
循环来遍历这个list
或tuple
,这种遍历我们称为迭代(Iteration)。
很多容器都是可迭代对象,此外还有更多的对象同样也是可迭代对象,比如处于打开状态的files
,sockets
等等。但凡是可以返回一个 迭代器 的对象都可称之为可迭代对象,听起来可能有点困惑,没关系,可迭代对象与迭代器有一个非常重要的区别。先看一个例子:
这里 x 是一个可迭代对象,可迭代对象和容器一样是一种通俗的叫法,并不是指某种具体的数据类型,list是可迭代对象,dict是可迭代对象,set也是可迭代对象。 y 和 z 是两个独立的迭代器,迭代器内部持有一个状态,该状态用于记录当前迭代所在的位置,以方便下次迭代的时候获取正确的元素。迭代器有一种具体的迭代器类型,比如 list_iterator , set_iterator 。可迭代对象实现了 iter 和 next 方法(python2中是 next 方法,python3是 next 方法),这两个方法对应内置函数 iter() 和 next() 。 iter 方法返回可迭代对象本身,这使得他既是一个可迭代对象同时也是一个迭代器。
迭代器
那么什么迭代器呢?它是一个带状态的对象,他能在你调用 next() 方法的时候返回容器中的下一个值,任何实现了 next() (python2中实现 next() )方法的对象都是迭代器,至于它是如何实现的这并不重要。
现在我们就以斐波那契数列为例,学习为何创建以及如何创建一个迭代器:
著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, …
def fab(max):
n, a, b = 0, 0, 1
while n < max:
print b
a, b = b, a + b
n = n + 1
直接在函数fab(max)
中用print
打印会导致函数的可复用性变差,因为fab
返回None
。其他函数无法获得fab
函数返回的数列。
def fab(max):
L = []
n, a, b = 0, 0, 1
while n < max:
L.append(b)
a, b = b, a + b
n = n + 1
return L
上面的代码满足了可复用性的需求,但是占用了内存空间,最好不要。
class Fab(object):
def __init__(self, max):
self.max = max
self.n, self.a, self.b = 0, 0, 1
def __iter__(self):
return self
def __next__(self):
if self.n < self.max:
r = self.b
self.a, self.b = self.b, self.a + self.b
self.n = self.n + 1
return r
raise StopIteration()
'''
>>> for key in Fab(5):
print key
++++++++++++++++++++++++++++++
1
1
2
3
5
Fab
类通过不断返回数列的下一个数,内存占用始终为常数。Fab
既是一个可迭代对象(因为它实现了 __iter__
方法),又是一个迭代器(因为实现了 __next__
方法)。实例变量 self.a
和 self.b
用户维护迭代器内部的状态。每次调用 __next__()
方法的时候做两件事:
- 为下一次调用
__next__()
方法修改状态 - 为当前这次调用生成返回结果
迭代器就像一个懒加载的工厂,等到有人需要的时候才给它生成值返回,没调用的时候就处于休眠状态等待下一次调用。
for i in (iterable)的内部实现
在大多数情况下,我们不会一次次调用next
方法去取值,而是通过 for i in (iterable)
注意:in
后面的对象如果是一个迭代器,内部因为有iter
方法才可以进行操作,所以,迭代器协议里面有iter
和next
两个方法,否则for语句无法应用。
所以for
循环内部实现为:
- 调用可迭代对象的
__iter__()
方法返回一个迭代器对象 - 不断调用迭代器对象的
__next__()
方法 - 处理
StopIteration
异常
生成器
生成器其实是一种特殊的迭代器,不过这种迭代器更加优雅。它不需要再像上面的类一样写 __iter__()
和 __next__()
方法了,只需要一个 yiled
关键字。
生成器有如下特征是它一定也是迭代器(反之不成立),因此任何生成器也是以一种懒加载的模式生成值。用生成器来实现斐波那契数列的例子:
def fab(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
for n in fab(5):
print n
+++++++++++++++++++++++++++++
1
1
2
3
5
fib 就是一个普通的python函数,它特需的地方在于函数体中没有 return 关键字,函数的返回值是一个生成器对象。当执行 f=fib(5)
返回的是一个生成器对象,此时函数体中的代码并不会执行,只有显示或隐示地调用next的时候才会真正执行里面的代码。
yield
的作用就是把一个函数变成一个 generator
,带有 yield
的函数不再是一个普通函数,Python
解释器会将其视为一个 generator
,在 for
循环执行时,每次循环都会执行 fab
函数内部的代码,执行到 yield b
时,fab
函数就返回一个迭代值,下次迭代时,代码从 yield b
的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield
。看起来就好像一个函数在正常执行的过程中被 yield
中断了数次,每次中断都会通过 yield
返回当前的迭代值。
也可以手动调用 fab(5)
的 __next__()
方法(因为 fab(5)
是一个 generator
对象,该对象具有 __next__()
方法),这样我们就可以更清楚地看到 fab
的执行流程:
In [13]: f = fab(3)
In [14]: f.__next__()
Out[14]: 1
In [15]: f.__next__()
Out[15]: 1
In [16]: f.__next__()
Out[16]: 2
In [17]: f.__next__()
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-17-dcf180275632> in <module>()
----> 1 f.__next__()
StopIteration:
需要明确的就是生成器也是iterator
迭代器,因为它遵循了迭代器协议.
生成器的两种创建方式
s = (x**2 for x in range(20))
s 是一个生成器yield
关键字
生成器函数跟普通函数只有一点不一样,就是把 return
换成yield
,其中yield
是一个语法糖,内部实现了迭代器协议,同时保持状态可以挂起。
大文件读取
def read_file(fpath):
BLOCK_SIZE = 1024
with open(fpath, 'rb') as f:
while True:
block = f.read(BLOCK_SIZE)
if block:
yield block
else:
return
如果直接对文件对象调用 read()
方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件内容。通过 yield
,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取。
生成器对象就是一种特殊的迭代器对象,满足迭代器协议,可以调用next
;对生成器对象for
循环时,调用iter
方法返回了生成器对象,然后再不断next
迭代,而iter
和next
都是在yield
内部实现的。
练习1:使用文件读取,找出文件中最长行的长度?
max(len(x.strip()) for x in open('/hello/abc','r'))