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
生成器允許在執行時,外部自定義一個值傳入生成器内部,進而影響生成器的執行結果。
總結
- 疊代器必須實作疊代器協定iter和next/__next方法
- iter傳回疊代器的對象稱作可疊代對象
- 疊代器一定是個可疊代對象,但可疊代對象不一定是疊代器,有可能疊代細節傳遞給另一個類,這個類才是疊代器
- 生成器一定是一個疊代器,同時也是個可疊代對象
- 生成器是一種特殊的疊代器,yield關鍵字可實作懶惰計算,并使得外部影響生成器的執行成為了可能