可迭代对象(Iterables)
创建一个列表list时,你可以逐个地读取里面的每一项元素,这个过程称之为迭代(iteration)</font>
>>> mylist = [1, 2, 3]
>>> for i in mylist:
... print(i)
1
2
3
mylist是一个可迭代的对象,当使用列表推导式创建一个列表时,它就是可迭代对象。</font>
>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
... print(i)
0
1
4
任何可以使用在for...in...语句中的对象都叫做可迭代对象,例如:lists、strings、files等等。这些可迭代对象使用非常方便因为它们能如你所愿的尽可能的读取其中的元素,但是你不得不把所有的值存储在内存中,当它有大量元素的时候这并不一定总是你想要的。
dict对象已经任何实现了_iter()或者_getitem()方法的类都是可迭代对象,此外,可迭代对象还可以用在zip、map等函数中,当一个可迭代对象作为参数传递给內建函数iter()时,它会返回一个迭代器对象。通常没有必要自己来处理迭代器本身或者手动调用iter(),for语句会自动调用iter(),它会创建一个临时的未命名的变量来持有这个迭代器用于循环期间。</font>
迭代器(iterator)
迭代器代表一个数据流对象,不断重复调用迭代器的next()方法逐次的从返回数据流中的每一项,当没有更多数据可用时,next()方法会抛出异常StopIteration。此时迭代器对象已经枯竭了,之后调用next()方法都会抛出StopIteration。迭代器需要一个_iter_()方法用来返回迭代器本身,因此它也是一个可迭代的对象。</font>
生成器(Generators)
生成器也是一个迭代器,但是你只可以迭代它们一次,不能重复迭代,因为它并没有把所有的值存储在内存中,而是实时地生成值:</font>
>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
... print(i)
0
1
4
从结果上看用()代替[]的效果是一样的,但是你不可能第二次执行for i in mygenerator,因为生成器只能使用一次</font>
Yield
yield是关键字,它类似于return,只是函数会返回一个生成器。
>>> class Bank(): # 创建银行,构建ATM机,只要没有危机,就可以不断地每次从中取100
... crisis = False
... def create_atm(self):
... while not self.crisis:
... yield "$100"
>>> hsbc = Bank() # when everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # 危机来临,没有更多的钱了
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # 即使创建一个新的ATM,银行还是没钱
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # 危机过后,银行还是空的,因为该函数之前已经不满足while条件
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # 必须构建一个新的atm,恢复取钱业务
>>> for cash in brand_new_atm:
... print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...
对于类似资源的访问控制等场景,生成器显得很实用。
Itertools是你最好的朋友
itertools模块包含一些特殊的函数用来操作可迭代对象。曾经想复制一个生成器?两个生成器链接?在内嵌列表中一行代码处理分组?不会创建另外一个列表的Map/Zip函数?你要做的就是import itertools 。无例子无真相,我们来看看4匹马赛跑到达终点所有可能的顺序:
>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),
(1, 2, 4, 3),
(1, 3, 2, 4),
(1, 3, 4, 2),
(1, 4, 2, 3),
(1, 4, 3, 2),
(2, 1, 3, 4),
(2, 1, 4, 3),
(2, 3, 1, 4),
(2, 3, 4, 1),
(2, 4, 1, 3),
(2, 4, 3, 1),
(3, 1, 2, 4),
(3, 1, 4, 2),
(3, 2, 1, 4),
(3, 2, 4, 1),
(3, 4, 1, 2),
(3, 4, 2, 1),
(4, 1, 2, 3),
(4, 1, 3, 2),
(4, 2, 1, 3),
(4, 2, 3, 1),
(4, 3, 1, 2),
(4, 3, 2, 1)]
理解迭代器的内部机理
迭代是操作可迭代对象(实现了_iter()方法)和迭代器(实现了_next()方法)的过程。可迭代对象是任何你可以从其中得到一个迭代器对象的任意对象(调用內建函数_iter_()),迭代器是能让你在可迭代对象上进行迭代的对象。
一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,并返回一个迭代值,下次执行时从 yield 的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。
yield 的好处是显而易见的,把一个函数改写为一个 generator 就获得了迭代能力,比起用类的实例保存状态来计算下一个 next() 的值,不仅代码简洁,而且执行流程异常清晰。
使用Yield的一个例子,从文件读取内容。
如果直接对文件 对象调用read()方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件内容。通过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