1.可迭代的對象
定義:可迭代的對象(Iterable)是指使用iter()內(nèi)置函數(shù)可以獲取迭代器(Iterator)的對象
- Python解釋器需要迭代對象x時,會自動調(diào)用iter(x),內(nèi)置的iter()函數(shù)有以下作用:
(1) 檢查對象x是否實現(xiàn)了iter()方法,如果實現(xiàn)了該方法就調(diào)用它,并嘗試獲取一個迭代器
(2) 如果沒有實現(xiàn)iter()方法,但是實現(xiàn)了getitem(index)方法,嘗試按順序(從索引0開始)獲取元素,即參數(shù)index是從0開始的整數(shù)(int)。之所以會檢查是否實現(xiàn)getitem(index)方法,為了向后兼容
(3) 如果前面都嘗試失敗,Python會拋出TypeError異常,通常會提示'X' object is not iterable(X類型的對象不可迭代),其中X是目標對象所屬的類 - 具體來說,哪些是可迭代對象呢?
(1) 如果對象實現(xiàn)了能返回迭代器的iter()方法,那么對象就是可迭代的
(2) 如果對象實現(xiàn)了getitem(index)方法,而且index參數(shù)是從0開始的整數(shù)(索引),這種對象也可以迭代的。Python中內(nèi)置的序列類型,如list、tuple、str、bytes、dict、set、collections.deque等都可以迭代,原因是它們都實現(xiàn)了getitem()方法(注意: 其實標準的序列還都實現(xiàn)了iter()方法)
1.1 判斷對象是否可迭代
檢查對象x能否迭代,最準確的方法是:調(diào)用iter(x)函數(shù),如果不可迭代,會拋出TypeError異常。這比使用isinstance(x, abc.Iterable)更準確,因為iter(x)函數(shù)會考慮到遺留的__getitem__(index)方法,而abc.Iterable類則不會考慮
1.2 __getitem__()
下面構造一個類,它實現(xiàn)了__getitem__()方法??梢越o類的構造方法傳入包含一些文本的字符串,然后可以逐個單詞進行迭代:
'''創(chuàng)建test.py模塊'''
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 __getitem__(self, index):
return self.words[index]
def __len__(self): # 為了讓對象可以迭代沒必要實現(xiàn)這個方法,這里是為了完善序列協(xié)議,即可以用len(s)獲取單詞個數(shù)
return len(self.words)
def __repr__(self):
return 'Sentence({})'.format(reprlib.repr(self.text))
測試Sentence實例能否迭代:
In [1]: from test import Sentence # 導入剛創(chuàng)建的類
In [2]: s = Sentence('I love Python') # 傳入字符串,創(chuàng)建一個Sentence實例
In [3]: s
Out[3]: Sentence('I love Python')
In [4]: s[0]
Out[4]: 'I'
In [5]: s.__getitem__(0)
Out[5]: 'I'
In [6]: for word in s: # Sentence實例可以迭代
...: print(word)
...:
I
love
Python
In [7]: list(s) # 因為可以迭代,所以Sentence對象可以用于構建列表和其它可迭代的類型
Out[7]: ['I', 'love', 'Python']
In [8]: from collections import abc
In [9]: isinstance(s, abc.Iterable) # 不能正確判斷Sentence類的對象s是可迭代的對象
Out[9]: False
In [10]: iter(s) # 沒有拋出異常,返回迭代器,說明Sentence類的對象s是可迭代的
Out[10]: <iterator at 0x7f82a761e5f8>
1.3. __iter__()
用法:
__iter__()
\t return 返回迭代器
1.4 iter()函數(shù)的補充
iter()函數(shù)有兩種用法:
- iter(iterable) -> iterator: 傳入可迭代的對象,返回迭代器
- iter(callable, sentinel) -> iterator: 傳入兩個參數(shù),第一個參數(shù)必須是可調(diào)用的對象,用于不斷調(diào)用(沒有參數(shù)),產(chǎn)出各個值;第二個值是哨符,這是個標記值,當可調(diào)用的對象返回這個值時,觸發(fā)迭代器拋出 StopIteration 異常,而不產(chǎn)出哨符
2. 迭代器
一種惰性獲取數(shù)據(jù)項的方式,即按需一次獲取一個數(shù)據(jù)項。這就是迭代器模式(Iterator pattern)
迭代器是這樣的對象:實現(xiàn)了無參數(shù)的__next__()方法,返回序列中的下一個元素,如果沒有元素了,就拋出StopIteration異常。即,迭代器可以被next()函數(shù)調(diào)用,并不斷返回下一個值
在 Python 語言內(nèi)部,迭代器用于支持: for 循環(huán)。構建和擴展集合類型。逐行遍歷文本文件。列表推導、字典推導和集合推導。元組拆包。調(diào)用函數(shù)時,使用 * 拆包實參
2.1 判斷對象是否為迭代器
檢查對象x是否為迭代器最好的方式是調(diào)用isinstance(x, abc.Iterator)
Python中內(nèi)置的序列類型,如list、tuple、str、bytes、dict、set、collections.deque等都是可迭代的對象,但不是迭代器; 生成器一定是迭代器
2.2 __next__()和__iter__()
標準的迭代器接口:
-
__next__(): 返回下一個可用的元素,如果沒有元素了,拋出StopIteration異常。調(diào)用next(x)相當于調(diào)用x.__next__() -
__iter__(): 返回迭代器本身(self),以便在應該使用可迭代的對象的地方能夠使用迭代器,比如在for循環(huán)、list(iterable)函數(shù)、sum(iterable, start=0, /)函數(shù)等應該使用可迭代的對象地方可以使用迭代器。說明: 只要實現(xiàn)了能返回迭代器的__iter__()方法的對象就是可迭代的對象,所以,迭代器都是可迭代的對象!
2.3 next()函數(shù)獲取迭代器中下一個元素
除了可以使用for循環(huán)處理迭代器中的元素以外,還可以使用next()函數(shù),它實際上是調(diào)用iterator.__next__(),每調(diào)用一次該函數(shù),就返回迭代器的下一個元素。如果已經(jīng)是最后一個元素了,再繼續(xù)調(diào)用next()就會拋出StopIteration異常。一般來說,StopIteration異常是用來通知我們迭代結束的
或者,為next()函數(shù)指定第二個參數(shù)(默認值),當執(zhí)行到迭代器末尾后,返回默認值,而不是拋出異常
2.4 可迭代的對象與迭代器的對比
-
可迭代的對象和迭代器之間的關系:Python從可迭代的對象中獲取迭代器 - 用
for循環(huán)迭代一個字符串'ABC',字符串是可迭代的對象。for循環(huán)的背后會先調(diào)用iter(s)將字符串轉換成迭代器,只不過我們看不到 -
StopIteration異常表明迭代器到頭了,Python語言內(nèi)部會處理for循環(huán)和其它迭代上下文(如列表推導、元組拆包等)中的StopIteration異常
總結:
(1)迭代器要實現(xiàn)__next__()方法,返回迭代器中的下一個元素
(2)迭代器還要實現(xiàn)__iter__()方法,返回迭代器本身,因此,迭代器可以迭代。迭代器都是可迭代的對象
(3)可迭代的對象一定不能是自身的迭代器。也就是說,可迭代的對象必須實現(xiàn)__iter__()方法,但不能實現(xiàn)__next__()方法
3. 生成器
在Python中,可以使用生成器讓我們在迭代的過程中不斷計算后續(xù)的值,而不必將它們?nèi)看鎯υ趦?nèi)存中
3.1 生成器函數(shù)
- 只要 Python 函數(shù)的定義體中有
yield關鍵字,該函數(shù)就是生成器函數(shù)。調(diào)用生成器函數(shù)時,會返回一個生成器(generator)對象。也就是說,生成器函數(shù)是生成器工廠 -
普通的函數(shù)與生成器函數(shù)在語法上唯一的區(qū)別是,在后者的定義體中有yield關鍵字 - 調(diào)用生成器函數(shù)后會創(chuàng)建一個新的生成器對象,但是此時還不會執(zhí)行函數(shù)體。
-
next(g)時,會激活生成器,生成器函數(shù)會向前前進到 函數(shù)定義體中的下一個 yield 語句,生成 yield關鍵字后面的表達式的值,在函數(shù)定義體的當前位置暫停,并返回生成的值 - 注意用詞: 普通函數(shù)返回值,調(diào)用生成器函數(shù)返回生成器,生成器產(chǎn)出或生成值
- 調(diào)用
生成器函數(shù)后,會構建一個實現(xiàn)了迭代器接口的生成器對象,即,生成器一定是迭代器! -
迭代器和生成器都是為了惰性求值(lazy evaluation),避免浪費內(nèi)存空間
3.2 生成器表達式
- 簡單的
生成器函數(shù)(有yield關鍵字),可以替換成生成器表達式(沒有yield關鍵字,將列表推導中的[]替換為()即可),讓代碼變得更簡短 -
生成器表達式可以理解為列表推導的惰性版本:不會迫切地構建列表,而是返回一個
生成器,按需惰性生成元素。也就是說,如果列表推導是制造列表的工廠,那么生成器表達式就是制造生成器的工廠 -
生成器像管道(pipeline)一樣鏈接起來使用,更高效的處理數(shù)據(jù)
3.3 增強生成器
使用.send()方法
使用方法:對象 . send(x) #x 為發(fā)送的數(shù)據(jù)
與.__next__()方法一樣,.send()方法使生成器前進到下一個yield語句。不過,.send()方法還允許調(diào)用方把數(shù)據(jù)發(fā)送給生成器,即不管傳給.send()方法什么參數(shù),那個參數(shù)都會成為生成器函數(shù)定義體中對應的yield表達式的值。也就是說,send()方法允許在調(diào)用方和生成器之間雙向交換數(shù)據(jù),而.__next__()方法只允許調(diào)用方從生成器中獲取數(shù)據(jù)
查看生成器對象的狀態(tài):
可以使用inspect.getgeneratorstate(...)函數(shù)查看生成器對象的當前狀態(tài):
- 'GEN_CREATED': 等待開始執(zhí)行
- 'GEN_RUNNING': 正在被解釋器執(zhí)行。只有在多線程應用中才能看到這個狀態(tài)
- 'GEN_SUSPENDED': 在yield表達式處暫停
- 'GEN_CLOSED': 執(zhí)行結束
3.4 yield from
yield from 后面需要加的是可迭代對象,它可以是普通的可迭代對象,也可以是迭代器,甚至是生成器。