http://kaito-kidd.com/2018/04/18/python-advance-iterator-generator/#more
在Python开发中,我们见过很常见的概念,如容器、可迭代对象、迭代器、生成器。
这些概念之间有什么联系和区别呢?
总览
容器(container)、可迭代对象(iterable)、迭代器(iterator)、生成器(generator)的关系如下图:
- list、set、tuple、dict都是容器
- 容器通常是一个可迭代对象
- 但凡可以返回一个迭代器的对象,都称之为可迭代对象
- 实现了迭代器协议方法的称作一个迭代器
- 生成器是一种特殊的迭代器
容器
简单来说,容器就是存储一些事物的概念统称,它最大的特性就是给你一个事物,告诉我这个事物是否在这个容器内。
在Python中,使用in或not in来判断某个事物是存在或不存在某个容器内。
也就是说一个对象实现了__contains__方法,我们都可以称之为容器。
在Python中,str、list、tuple、set、dict都是容器,因为我们可以用in或not in语法得知某个元素是否在容器内,它们内部都实现了__contains__方法。
print 1 in [1, 2, 3] # True
print 2 not in (1, 2, 3) # False
print 'a' in ('a', 'b', 'c') # True
print 'x' in 'xyz' # True
print 'a' not in 'xyz' # True
如果我们想自定义一个容器,只需像下面这样:
class A(object):
def __init__(self):
self.items = [1, 2]
def __contains__(self, item): # x in y
return item in self.items
a = A()
print 1 in a # True
print 2 in a # True
print 3 in a # False
但一个容器不一定支持输出存储在内的所有元素的功能。
一个容器要想输出保存在内的所有元素,其内部需要实现迭代器协议。
迭代器
一个对象如果实现了迭代器协议,就可以称之为迭代器。
在Python中实现迭代器协议,需要实现以下2个方法:
- iter,这个方法返回对象本身
- Python2中实现next,Python3中实现__next__,这个方法每次返回迭代的值,在没有可迭代元素时,抛出StopIteration异常 下面我们实现一个自定义的迭代器:
class A(object):
"""内部实现了迭代器协议,这个对象就是一个迭代器"""
def __init__(self, n):
self.idx = 0
self.n = n
def __iter__(self):
print '__iter__'
return self
def next(self):
if self.idx < self.n:
val = self.idx
self.idx += 1
return val
else:
raise StopIteration()
# 迭代元素
a = A(3)
for i in a:
print i
print '-------'
# 再次迭代,没有元素输出,迭代器只能迭代一次
for i in a:
print i
# __iter__
# 0
# 1
# 2
# -------
# __iter__
在执行for循环时,我们看到__iter__的打印被输出,然后依次输出next中的元素。
其实在执行for循环时,实际调用顺序是这样的:
for i in a => b = iter(a) => next(b) => next(b) ... => StopIteration => end
首选执行iter(a),iter会调用__iter__,在得到一个迭代器后,循环执行next,next会调用迭代器的next,在遇到StopIteration异常时,停止迭代。
但注意,再次执行迭代器,如果所有元素都已迭代完成,将不会再次迭代。
如果我们想每次执行都能迭代元素,只需在迭代时,执行的都是一个新的迭代器即可:
for i in A(3):
print i
# 每次执行一个新的迭代对象
for i in A(3):
print i
可迭代对象
但凡是可以返回一个迭代器的对象,都可以称之为可迭代对象。
这句话怎么理解?
可以翻译为:__iter__方法返回迭代器,这个对象就是可迭代对象。
我们在上面看到的迭代器,也就是说实现了__iter__和next/__next__方法的类,这些类的实例就是一个可迭代对象。
迭代器一定是个可迭代对象,但可迭代对象不一定是迭代器。
这句话怎么理解?我们看代码:
class A(object):
"""
A的实例不是迭代器,因为只A实现了__iter__
但这个类的实例是一个可迭代对象
因为__iter__返回了B的实例,也就是返回了一个迭代器,因为B实现了迭代器协议
返回一个迭代器的对象都被称为可迭代对象
"""
def __init__(self, n):
self.n = n
def __iter__(self):
return B(self.n)
class B(object):
"""这个类是个迭代器,因为实现了__iter__和next方法"""
def __init__(self, n):
self.idx = 0
self.n = n
def __iter__(self):
return self
def next(self):
if self.idx < self.n:
val = self.idx
self.idx += 1
return val
else:
raise StopIteration()
b = B(3) # b是一个迭代器,同时b是一个可迭代对象
for i in b:
print i
print iter(b) # <__main__.B object at 0x10eb95450>
a = A(3) # a不是迭代器,但a是可迭代对象,它把迭代细节交给了B,B的实例是迭代器
for i in a:
print i
print iter(a) # <__main__.B object at 0x10eb95550>
对于B:
- B的实例是一个迭代器,因为其实现了迭代器协议__iter__和next方法
- 同时B的__iter__方法返回了self实例本身,也就是说返回了一个迭代器,所以B的实例b也是一个可迭代对象
对于A:
- A的实例不是一个迭代器,因为没有同时满足__iter__和next方法
- 由于A的__iter__返回了B的实例,而B的实例是一个迭代器,所以A的实例a是一个可迭代对象,换句话说,A把迭代细节交给了B
其实我们使用的内置对象list、tuple、set、dict,都叫做可迭代对象,但不是一个迭代器,因为其内部都把迭代细节交给了另外一个类,这个类才是真正的迭代器:
l = [1, 2] # list是可迭代对象
iter(l) # list返回的迭代器是listiterator
<listiterator object at 0x10599c350>
iter(l).next() # 迭代器有next方法
1
t = ('a', 'b') # tuple是可迭代对象
iter(t) # tuple返回的迭代器是tupleiterator
<tupleiterator object at 0x10599c390>
iter(t).next() # 迭代器有next方法
a
s = {1, 2} # set是可迭代对象
iter(s) # set返回的迭代器是setiterator
<setiterator object at 0x10592df50> #
iter(s).next() # 迭代器有next方法
1
d = {'a': 1, 'b': 2} # dict是可迭代对象
iter(d) # dict返回的迭代器是dictionary-keyiterator
<dictionary-keyiterator object at 0x105977db8>
iter(d).next() # 迭代器有next方法
a
生成器
生成器是特殊的迭代器,它也是个可迭代对象。
有2种方式可以创建一个生成器:
- 生成器表达式
- 生成器函数
生成器表达式如下:
g = (i for i in range(5)) # 创建一个生成器
g
<generator object <genexpr> at 0x101334f50>
iter(g) # 生成器就是一个迭代器
<generator object <genexpr> at 0x101334f50>
for i in g: # 生成器也是一个可迭代对象
print i
# 0 1 2 3 4
生成器函数,包含yield关键字的函数:
def gen(n):
# 生成器函数
for i in range(n):
yield i
g = gen(5) # 创建一个生成器
print g # <generator object gen at 0x10bb46f50>
print type(g) # <type 'generator'>
# 迭代
for i in g:
print i
# 0 1 2 3 4
一般情况下,我们使用比较多的情况是以函数的方式创建生成器,也就是函数中使用yield关键字。
这个函数与包含return的函数执行机制不同:
- 包含return的方法会以return关键字为终结返回,每次执行都返回相同的结果
- 包含yield的方法一般用于迭代,每次执行遇到yield即返回yield后的结果,但内部会保留上次执行的状态,下次迭代继续执行yield之后的代码,直到再次遇到yield并返回
当我们想得到一个很大的集合时,如果使用普通方法,一次性生成出这个集合,然后return返回:
def gen_data(n):
return [i for i in range(n)] # 一次性生成大集合
但如果这个集合非常大时,就需要在内存中一次性占用非常大的内存。
使用yield能够完美解决这类问题,因为yield是懒惰执行的,一次只会返回一个值:
for gen_data(n):
for i in range(n):
yield i # 每次只生成一个元素
生成器在Python中还有更大的用处,我们来看生成器中还有哪些方法:
g = (i for i in range(3))
dir(g)
['__class__', '__init__', '__iter__', 'next', 'send', 'throw' ...
我们发现生成器中还包含了一个叫做send的方法,如何使用?
def gen(n):
for i in range(n):
a = yield i
if a == 100:
print 'a is 100'
a = gen(10) # 创建生成器
print a.next() # 0
print a.next() # 1
print a.send(100) # send(100)赋值给a 然后打印出'a is 100'
print a.next() # 2
生成器允许在执行时,外部自定义一个值传入生成器内部,从而影响生成器的执行结果。
正因为有了这个机制,Python的生成器在开发中有了很大的应用场景,我们后面单独讲它的具体使用场景和优点。
总结
这里总结一下迭代器、可迭代对象、生成器它们之间的联系与区别:
- 迭代器必须实现迭代器协议__iter__和next/__next方法
- __iter__返回迭代器的对象称作可迭代对象
- 迭代器一定是个可迭代对象,但可迭代对象不一定是迭代器,有可能迭代细节交付给另一个类,这个类才是迭代器
- 生成器一定是一个迭代器,同时也是个可迭代对象
- 生成器是一种特殊的迭代器,yield关键字可实现懒惰计算,并使得外部影响生成器的执行成为了可能
如果此文章能给您带来小小的工作效率提升,不妨小额赞助我一下,以鼓励我写出更好的文章!