一、可迭代對象
可迭代的對象(Iterable)是指使用iter()內(nèi)置函數(shù)可以獲取迭代器(Iterator)的對象
1. 判斷是否是可迭代對象:
- 如果對象實現(xiàn)了能返回迭代器的
__iter__()
方法,那么對象就是可迭代的 - 如果對象實現(xiàn)了
__getitem__(index)
方法,而且index參數(shù)是從0開始的整數(shù)(索引)渠啊,這種對象也可以迭代的。Python中內(nèi)置的序列類型,如list炼绘、tuple填帽、str徒欣、bytes、dict澈侠、set形庭、collections.deque等都可以迭代铅辞,原因是它們都實現(xiàn)了__getitem__()
方法(注意: 其實標(biāo)準(zhǔn)的序列還都實現(xiàn)了__iter__()
方法)
從Python 3.4開始,檢查對象x能否迭代萨醒,最準(zhǔn)確的方法是:調(diào)用iter(x)
函數(shù)斟珊,如果不可迭代,會拋出TypeError
異常富纸。這比使用isinstance(x, abc.Iterable)
更準(zhǔn)確囤踩,因為iter(x)函數(shù)會考慮到遺留的__getitem__(index)
方法,而abc.Iterable
類則不會考慮
2. iter()函數(shù)有兩種用法:
- iter(iterable) -> iterator: 傳入可迭代的對象晓褪,返回迭代器
- iter(callable, sentinel) -> iterator: 傳入兩個參數(shù)堵漱,第一個參數(shù)必須是可調(diào)用的對象,用于不斷調(diào)用(沒有參數(shù))涣仿,產(chǎn)出各個值勤庐;第二個值是哨符,這是個標(biāo)記值好港,當(dāng)可調(diào)用的對象返回這個值時愉镰,觸發(fā)迭代器拋出 StopIteration 異常,而不產(chǎn)出哨符
注:
- Python迭代協(xié)議要求iter()必須返回特殊的迭代器對象钧汹。
- 如果類里有
__iter__
方法丈探,且__iter__
能返回一個迭代器,其對象就是可迭代對象 - 檢查對象x是否為可迭代對象最準(zhǔn)確的方法拔莱,就是調(diào)用iter(x)方法
- iter(callable,sentinel)兩個參數(shù)(可調(diào)用對象碗降,哨符) 隘竭, 重復(fù)調(diào)用callable,產(chǎn)出各個值遗锣,直到返回的值為哨符就會停止迭代货裹,拋出異常StopIteration,而不產(chǎn)出哨符
3. 實用的示例:
逐行讀取文件精偿,直到遇到空行或者到達文件末尾為止
with open('mydata.txt') as fp:
for line in iter(fp.readline, '\n'): # fp.readline每次返回一行
print(line)
二弧圆、迭代器
迭代是數(shù)據(jù)處理的基石。
當(dāng)掃描內(nèi)存中放不下的數(shù)據(jù)集時笔咽,我們要找到一種惰性獲取數(shù)據(jù)項(省內(nèi)存)的方式搔预,即按需一次獲取一個數(shù)據(jù)項。
這就是迭代器模式(Iterator pattern)
迭代器
是這樣的對象:實現(xiàn)了無參數(shù)的__next__()
方法叶组,返回序列中的下一個元素拯田,如果沒有元素了,就拋出StopIteration
異常甩十。
即船庇,迭代器可以被next()函數(shù)調(diào)用,并不斷返回下一個值侣监。
在 Python 語言內(nèi)部鸭轮,迭代器用于支持:
- for 循環(huán)
- 構(gòu)建和擴展集合類型
- 逐行遍歷文本文件
- 列表推導(dǎo)、字典推導(dǎo)和集合推導(dǎo)
- 元組拆包
- 調(diào)用函數(shù)時橄霉,使用 * 拆包實參
1. 判斷對象是否為迭代器
檢查對象x是否為迭代器最好的方式是調(diào)用 isinstance(x, abc.Iterator) :
In [1]: from collections import abc
In [2]: isinstance([1,3,5], abc.Iterator)
Out[2]: False
In [3]: isinstance((2,4,6), abc.Iterator)
Out[3]: False
In [4]: isinstance({'name': 'wangy', 'age': 18}, abc.Iterator)
Out[4]: False
In [5]: isinstance({1, 2, 3}, abc.Iterator)
Out[5]: False
In [6]: isinstance('abc', abc.Iterator)
Out[6]: False
In [7]: isinstance(100, abc.Iterator)
Out[7]: False
In [8]: isinstance((x*2 for x in range(5)), abc.Iterator) # 生成器表達式窃爷,后續(xù)會介紹
Out[8]: True
注:
Python中內(nèi)置的序列類型,如list姓蜂、tuple按厘、str、bytes钱慢、dict逮京、set、collections.deque等都是可迭代的對象束莫,但不是迭代器造虏; 生成器一定是迭代器
2. __next__()
和__iter__()
標(biāo)準(zhǔn)的迭代器
接口:
-
__next__()
: 返回下一個可用的元素,如果沒有元素了麦箍,拋出StopIteration異常。調(diào)用next(x)相當(dāng)于調(diào)用x.__next__()
-
__iter__()
: 返回迭代器本身(self)陶珠,以便在應(yīng)該使用可迭代的對象的地方能夠使用迭代器挟裂,比如在for循環(huán)、list(iterable)函數(shù)揍诽、sum(iterable, start=0, /)函數(shù)等應(yīng)該使用可迭代的對象地方可以使用迭代器诀蓉。
說明:只要實現(xiàn)了能返回迭代器的__iter__()
方法的對象就是可迭代的對象栗竖,所以,迭代器都是可迭代的對象渠啤!
示例:
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self, text):
self.text = text
self.words = RE_WORD.findall(text)
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self):
return SentenceIterator(self.words) # 迭代協(xié)議要求__iter__返回一個迭代器
class SentenceIterator:
def __init__(self, words):
self.words = words
self.index = 0
def __next__(self):
try:
word = self.words[self.index] # 獲取 self.index 索引位(從0開始)上的單詞狐肢。
except IndexError:
raise StopIteration() # 如果 self.index 索引位上沒有單詞,那么拋出 StopIteration 異常
self.index += 1
return word
def __iter__(self):
return self # 返回迭代器本身
注:
- 可以用next()對迭代器進行迭代沥曹,如:hello份名,調(diào)用一次next()就可以取到h,在調(diào)用一次取到e
- next()只能對迭代器使用
- 如果實現(xiàn)了
__iter__()
和__next__()
就是一個迭代器 - with open('') as f: f也是一個迭代器妓美,可以使用next(f)
- next(迭代器,默認(rèn)值)可以兩個參數(shù)僵腺,迭代完會返回默認(rèn)值,不會拋出異常
3. 可迭代的對象與迭代器的對比
首先壶栋,我們要明確可迭代的對象和迭代器之間的關(guān)系:
Python從可迭代的對象中獲取迭代器
比如辰如,用for循環(huán)迭代一個字符串'ABC',字符串是可迭代的對象贵试。for循環(huán)的背后會先調(diào)用iter(s)將字符串轉(zhuǎn)換成迭代器琉兜,只不過我們看不到:
In [1]: s = 'ABC'
In [2]: for char in s:
: print(char)
A
B
C
總結(jié):
- 迭代器要實現(xiàn)
__next__()
方法,返回迭代器中的下一個元素 - 迭代器還要實現(xiàn)
__iter__()
方法毙玻,返回迭代器本身豌蟋,因此,迭代器可以迭代淆珊。迭代器都是可迭代的對象 - 可迭代的對象一定不能是自身的迭代器夺饲。也就是說,可迭代的對象必須實現(xiàn)
__iter__()
方法施符,但不能實現(xiàn)__next__()
方法
三往声、生成器
在Python中,可以使用生成器讓我們在迭代的過程中不斷計算后續(xù)的值戳吝,而不必將它們?nèi)看鎯υ趦?nèi)存中:
'''斐波那契數(shù)列由0和1開始浩销,之后的費波那契系數(shù)就是由之前的兩數(shù)相加而得出,它是一個無窮數(shù)列'''
def fib(): # 生成器函數(shù)
a, b = 0, 1
while True:
yield a
a, b = b, a + b
g = fib() # 調(diào)用生成器函數(shù)听哭,返回一個實現(xiàn)了迭代器接口的生成器對象慢洋,生成器一定是迭代器
counter = 1
for i in g: # 可以迭代生成器
print(i) # 每需要一個值時,才會去計算生成
counter += 1
if counter > 10: # 只生成斐波那契數(shù)列前10個數(shù)值
break
1. 生成器函數(shù)
只要 Python 函數(shù)的定義體中有 yield
關(guān)鍵字陆盘,該函數(shù)就是生成器函數(shù)
普筹。調(diào)用生成器函數(shù)時,會返回一個生成器(generator)對象隘马。也就是說太防,生成器函數(shù)是生成器工廠
In [1]: def gen_AB(): # 定義生成器函數(shù)的方式與普通的函數(shù)無異,只不過要使用 yield 關(guān)鍵字
...: print('start')
...: yield 'A'
...: print('continue')
...: yield 'B'
...: print('end')
...:
In [2]: gen_AB # 生成器函數(shù)
Out[2]: <function __main__.gen_AB()>
In [3]: g = gen_AB() # 調(diào)用生成器函數(shù)酸员,返回一個生成器對象蜒车,注意:此時并不會執(zhí)行生成器函數(shù)定義體中的代碼讳嘱,所以看不到打印start
In [4]: g
Out[4]: <generator object gen_AB at 0x04CA74E0>
In [5]: next(g) # 生成器都是迭代器,執(zhí)行next(g)時生成器函數(shù)會向前酿愧,前進到函數(shù)定義體中的下一個 yield 語句沥潭,生成 yield 關(guān)鍵字后面的表達式的值,在函數(shù)定義體的當(dāng)前位置暫停嬉挡,并返回生成的值
start
Out[5]: 'A'
In [6]: next(g)
continue
Out[6]: 'B'
In [7]: next(g)
end
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-7-e734f8aca5ac> in <module>()
----> 1 next(g)
StopIteration:
調(diào)用生成器函數(shù)
后會創(chuàng)建一個新的生成器
對象钝鸽,但是此時還不會執(zhí)行函數(shù)體。
第一次執(zhí)行next(g)
時棘伴,會激活生成器寞埠,生成器函數(shù)會向前 前進到
函數(shù)定義體中的下一個 yield 語句,生成
yield關(guān)鍵字后面的表達式的值焊夸,在函數(shù)定義體的當(dāng)前位置暫停仁连,并返回生成的值。具體為:
- 執(zhí)行print('start')輸出start
- 執(zhí)行yield 'A'阱穗,此處yield關(guān)鍵字后面的表達式為'A'饭冬,即表達式的值為A。所以整條語句會
生成
值A(chǔ)揪阶,在函數(shù)定義體的當(dāng)前位置暫停昌抠,并返回值A(chǔ),我們在控制臺上看到輸出A
第二次執(zhí)行next(g)時鲁僚,生成器函數(shù)定義體中的代碼由 yield 'A' 前進到 yield 'B'炊苫,所以會先輸出continue,并生成值B冰沙,又在函數(shù)定義體的當(dāng)前位置暫停侨艾,返回值B
第三次執(zhí)行next(g)時,由于函數(shù)體中沒有另一個 yield 語句拓挥,所以前進到生成器函數(shù)的末尾唠梨,會先輸出end。到達生成器函數(shù)定義體的末尾時侥啤,生成器對象拋出StopIteration異常
注:
- 普通函數(shù)返回值当叭,調(diào)用生成器函數(shù)返回生成器,生成器產(chǎn)出或生成值
- 調(diào)用生成器函數(shù)后盖灸,會構(gòu)建一個實現(xiàn)了迭代器接口的生成器對象蚁鳖,即,
生成器一定是迭代器赁炎!
- 迭代器和生成器都是為了
惰性求值
(lazy evaluation)才睹,避免浪費內(nèi)存
空間。 - yield(產(chǎn)出)關(guān)鍵字 只要定義的函數(shù)里有yield就是一個生成器函數(shù),可以生成一個生成器對象
- 函數(shù)執(zhí)行時琅攘,遇到y(tǒng)ield會阻塞一下,返回yield的值松邪,類似return,但是yield返回值后可以繼續(xù)執(zhí)行函數(shù)
2. 生成器表達式
生成器表達式坞琴,與列表推導(dǎo)式類似,不過是()逗抑,最后產(chǎn)出一個生成器
如果列表推導(dǎo)是制造列表的工廠剧辐,那么生成器表達式就是制造生成器的工廠
In [1]: list((x*2 for x in range(5)))
Out[1]: [0, 2, 4, 6, 8]
In [2]: list(x*2 for x in range(5)) # 可以省略生成器表達式外面的()
Out[2]: [0, 2, 4, 6, 8]
3. 嵌套的生成器
可以將多個生成器
像管道(pipeline)
一樣鏈接起來使用,更高效的處理數(shù)據(jù):
In [1]: def integers(): # 1. 產(chǎn)出整數(shù)的生成器
...: for i in range(1, 9):
...: yield i
...:
In [2]: chain = integers()
In [3]: list(chain)
Out[3]: [1, 2, 3, 4, 5, 6, 7, 8]
In [4]: def squared(seq): # 2. 基于整數(shù)的生成器邮府,產(chǎn)出平方數(shù)的生成器
...: for i in seq:
...: yield i * i
...:
In [5]: chain = squared(integers())
In [6]: list(chain)
Out[6]: [1, 4, 9, 16, 25, 36, 49, 64]
In [7]: def negated(seq): # 3. 基于平方數(shù)的生成器荧关,產(chǎn)出負(fù)的平方數(shù)的生成器
...: for i in seq:
...: yield -i
...:
In [8]: chain = negated(squared(integers())) # 鏈?zhǔn)缴善鳎咝?
In [9]: list(chain)
Out[9]: [-1, -4, -9, -16, -25, -36, -49, -64]
由于上面各生成器函數(shù)的功能都非常簡單褂傀,所以可以使用生成器表達式進一步優(yōu)化鏈?zhǔn)缴善鳎?/p>
In [1]: integers = range(1, 9)
In [2]: squared = (i * i for i in integers)
In [3]: negated = (-i for i in squared)
In [4]: negated
Out[4]: <generator object <genexpr> at 0x7f2a5c09be08>
In [5]: list(negated)
Out[5]: [-1, -4, -9, -16, -25, -36, -49, -64]
4. 增強生成器
與.__next__()
方法一樣忍啤,.send()
方法使生成器
前進到下一個yield
語句。不過仙辟,.send()
方法還允許調(diào)用方
把數(shù)據(jù)發(fā)送給生成器
同波,即不管傳給.send()
方法什么參數(shù),那個參數(shù)都會成為生成器函數(shù)
定義體中對應(yīng)的yield表達式
的值叠国。也就是說未檩,.send()
方法允許在調(diào)用方
和生成器
之間雙向交換數(shù)據(jù),而.__next__()
方法只允許調(diào)用方
從生成器
中獲取數(shù)據(jù)
查看生成器對象的狀態(tài):
- 'GEN_CREATED': 等待開始執(zhí)行
- 'GEN_RUNNING': 正在被解釋器執(zhí)行粟焊。只有在多線程應(yīng)用中才能看到這個狀態(tài)
- 'GEN_SUSPENDED': 在yield表達式處暫停
- 'GEN_CLOSED': 執(zhí)行結(jié)束
In [1]: def echo(value=None):
...: print("Execution starts when 'next()' is called for the first time.")
...: try:
...: while True:
...: try:
...: value = (yield value) # 調(diào)用send(x)方法后冤狡,等號左邊的value將被賦值為x
...: except Exception as e:
...: value = e
...: finally:
...: print("Don't forget to clean up when 'close()' is called.")
...:
In [2]: g = echo(1) # 返回生成器對象,此時value=1
In [3]: import inspect
In [4]: inspect.getgeneratorstate(g)
Out[4]: 'GEN_CREATED'
In [5]: print(next(g)) # 第一次要調(diào)用next()方法项棠,讓生成器前進到第一個yield處悲雳,后續(xù)才能在調(diào)用send()方法時,在該yield表達式位置接收客戶發(fā)送的數(shù)據(jù)
Execution starts when 'next()' is called for the first time.
Out[5]: 1 # (yield value)沾乘,產(chǎn)出value的值怜奖,因為此時value=1,所以打印1
In [6]: inspect.getgeneratorstate(g)
Out[6]: 'GEN_SUSPENDED'
In [7]: print(next(g)) # 第二次調(diào)用next()方法翅阵,相當(dāng)于調(diào)用send(None)歪玲,所以value = (yield value)中等號左邊的value將被賦值為None。下一次While循環(huán)掷匠,又前進到(yield value)處滥崩,產(chǎn)出value的值,因為此時value=None讹语,所以打印None
None
In [8]: inspect.getgeneratorstate(g)
Out[8]: 'GEN_SUSPENDED'
In [9]: print(g.send(2)) # 直接調(diào)用send(2)方法钙皮,所以value = (yield value)中等號左邊的value將被賦值為2。下一次While循環(huán),又前進到(yield value)處短条,產(chǎn)出value的值导匣,因為此時value=2,所以打印2
2
In [10]: g.throw(TypeError, "spam") # 調(diào)用throw()方法茸时,將異常對象發(fā)送給生成器贡定,所以except語句會捕獲異常,即value=TypeError('spam')可都。下一次While循環(huán)缓待,又前進到(yield value)處,產(chǎn)出value的值渠牲,因為此時value=TypeError('spam')旋炒,所以打印TypeError('spam')
Out[10]: TypeError('spam')
In [11]: g.close() # 調(diào)用close()方法,關(guān)閉生成器
Don't forget to clean up when 'close()' is called.
In [12]: inspect.getgeneratorstate(g)
Out[12]: 'GEN_CLOSED'
這是一項重要的 "改進"签杈,甚至改變了生成器的本性:像這樣使用的話瘫镇,生成器就變身為基于生成器的協(xié)程。
注:給已結(jié)束的生成器發(fā)送任何值芹壕,都將拋出StopIteration異常汇四,且返回值(保存在異常對象的value屬性上)是None
5. yield from
yield from 后面需要加的是可迭代對象,它可以是普通的可迭代對象踢涌,也可以是迭代器通孽,甚至是生成器。
他可以把可迭代對象里的每個元素一個一個的yield出來睁壁,對比yield來說代碼更加簡潔背苦,結(jié)構(gòu)更加清晰。
yield from它可以幫我們處理異常
小結(jié)
- Python中內(nèi)置的序列潘明,如list行剂、tuple、str钳降、bytes厚宰、dict、set遂填、collections.deque等都是可迭代對象铲觉,但它們不是迭代器。
- 迭代器可以被 next() 函數(shù)調(diào)用吓坚,并不斷返回下一個值撵幽。
- Python從可迭代的對象中獲取迭代器。
- 迭代器和生成器都是為了惰性求值(lazy evaluation)礁击,避免浪費內(nèi)存空間盐杂,實現(xiàn)高效處理大量數(shù)據(jù)逗载。
- 在Python 3中,生成器有廣泛的用途链烈,所有生成器都是迭代器厉斟,因為生成器完全實現(xiàn)了迭代器接口。
- 迭代器用于從集合中取出元素测垛,而生成器用于"憑空"生成元素 捏膨。
- PEP 342 給生成器增加了 send() 方法,實現(xiàn)了"基于生成器的協(xié)程"食侮。
- PEP 380允許生成器中可以return返回值,并新增了 yield from 語法結(jié)構(gòu)目胡,打開了調(diào)用方和子生成器的雙向通道