可迭代对象 Iterable objects
实现可迭代协议的对象,称为可迭代对象。如list, tuple, dict, string等。
可迭代协议的定义是:
含__iter__()方法。且可迭代对象中的__iter__()方法返回的是一个对应的迭代器。
>>> from collections import Iterable
>>> help(Iterable)
Help on class Iterable in module _abcoll:
class Iterable(__builtin__.object)
| Methods defined here:
|
| __iter__(self)
我们可以用for语句,遍历一个可迭代对象:
>>> for i in [1, 2, 3, 4]:
... print(i)
...
1
2
3
4
当然,还有很多有用的函数,他们接受的参数,就是可迭代对象,例如sum,join等
>>> ",".join(("a", "b", "c"))
'a,b,c'
>>> sum((1,2,3))
6
迭代器 Iterator
满足迭代器协议的对象,称为迭代器对象。
迭代器协议约定如下:
- 含
__iter__()方法。且方法返回的Iterator对象本身 - 含
next()(python3是__next__())方法,每当next()方法被调用,返回下一个值,直到没有值可以访问,这个时候会抛出stopinteration的异常。
>>> from collections import Iterator
>>> help(Iterator)
Help on class Iterator in module _abcoll:
class Iterator(Iterable)
| Method resolution order:
| Iterator
| Iterable
| __builtin__.object
|
| Methods defined here:
|
| __iter__(self)
|
| next(self)
| Return the next item from the iterator. When exhausted, raise StopIteration
从上面的代码可以看出,Iterator继承自Iterabale。
下面类MyRange是一个迭代器对象,是一个xrange的实现:
class MyRange(object):
def __init__(self, end):
self.end = end
self.pivot = 0
def __iter__(self):
return self
def next(self):
if self.pivot < self.end:
i = self.pivot
self.pivot += 1
return i
else:
raise StopIteration()
for i in MyRange(10):
print i
可迭代对象和迭代器对象,通过函数iter转换。当调用函数iter函数时,就是调用Iterable对象的__iter__方法,它返回的是一个Iterator对象。
上面的for循环代码, 等价于:
my_range = MyRange(10)
iterator = iter(my_range) # 调用MyRange的__iter__方法
while True:
try:
print next(iterator)
except StopIteration as e:
break
为什么list、dict、str等数据类型不是Iterator?
这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
如上面例子中,我们通过for遍历了整数0到9,但我们并没有实际构造从0到9的数组。这就是迭代器的魅力。
for i in MyRange(10):
print i
生成器 generator
generator是Iterator的一种实现。他的出现,仅仅是为了便于编写Iterator。
Help on class generator in module __builtin__:
class generator(object)
| Methods defined here:
|
| __getattribute__(...)
| x.__getattribute__('name') <==> x.name
|
| __iter__(...)
| x.__iter__() <==> iter(x)
|
| __repr__(...)
| x.__repr__() <==> repr(x)
|
| close(...)
| close() -> raise GeneratorExit inside generator.
|
| next(...)
| x.next() -> the next value, or raise StopIteration
|
| send(...)
| send(arg) -> send 'arg' into generator,
| return next yielded value or raise StopIteration.
|
| throw(...)
| throw(typ[,val[,tb]]) -> raise exception in generator,
| return next yielded value or raise StopIteration.
|
从上面的定义中,可见,generator实现了Iterator必需的__iter__和next方法。
获得generator对象的一种方式,是把一个列表生成式的[]改成()
>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>
>>> for n in g:
... print(n)
...
0
1
4
9
16
25
36
49
64
81
获得generator对象的另一种方式,就是调用包含yield关键字的函数。即,如果一个函数定义中包含yield关键字,那么调用这个函数时,返回的是一个generator。
def foo():
... print("starting...")
... while True:
... res = yield 4
... print("res:",res)
... g = foo()
type(g)
<type 'generator'>
yield关键字,首先他拥有和return一样的功能,即在执行到yield时,程序返回某个值,返回之后不再往下运行。yield的第二个作用是,当对generator调用next时,从上次yield返回的地方开始执行。
我们用一段代码解释yield的执行顺序:
def foo():
print "starting..."
while True:
res = yield 4
print "res:",res
g = foo()
print next(g)
print next(g)
########## output #############
starting...
4
res: None
4
解释一下上面代码的执行属性:
-
程序开始执行以后,因为foo函数中有yield关键字,所以foo函数并不会真的执行,而是先得到一个生成器g(相当于一个对象)
-
直到调用next方法,foo函数正式开始执行,先执行foo函数中的print方法,然后进入while循环
-
程序遇到yield关键字,然后把yield想想成return,return了一个4之后,程序停止,并没有执行赋值给res操作,此时next(g)语句执行完成,所以输出的前两行(第一个是while上面的print的结果,第二个是return出的结果)是执行print(next(g))的结果
-
又开始执行下面的print(next(g)),这个时候和上面那个差不多,不过不同的是,这个时候是从刚才那个next程序停止的地方开始执行的,也就是要执行res的赋值操作,这时候要注意,这个时候赋值操作的右边是没有值的(因为刚才那个是return出去了,并没有给赋值操作的左边传参数),所以这个时候res赋值是None,所以接着下面的输出就是res:None,
-
程序会继续在while里执行,又一次碰到yield,这个时候同样return 出4,然后程序停止,print函数输出的4就是这次return出的4.
前面说过, generator的产生,是为了简化Iterator的实现。我们用generator实现前面的MyRange。
def my_range(n):
i = 0
while i < n:
yield i
i += 1
除此之外,generator还允许在迭代过程中,传递参数,用send实现。
def foo():
print "starting..."
while True:
res = yield 4
print "res:",res
g = foo()
print next(g)
print g.send(7)
########## output #############
starting...
4
res: 7
4
从上面的输出,可以理解到:用send的话,开始执行的时候,先接着上一次(return 4之后)执行,先把7赋值给了res,然后执行next的作用,遇见下一回的yield,return出结果后结束。
程序执行g.send(7),程序会从yield关键字那一行继续向下运行,send会把7这个值赋值给res变量
send的好处就是,在外层迭代时候,可以控制generator的遍历规则。