Python 可迭代對象、迭代器、生成器

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 后面需要加的是可迭代對象,它可以是普通的可迭代對象,也可以是迭代器,甚至是生成器。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
禁止轉載,如需轉載請通過簡信或評論聯(lián)系作者。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容