可迭代對象、迭代器、生成器的理解


所有的生成器都是迭代器

關於迭代器和生成器的一種定義:迭代器用於從集合中取出元素;生成器用於憑空生成元素。

Python中,所有的集合都是可以迭代的,在Python語言內部,迭代器用於支持:

  • for 循環
  • 構建和擴展集合類型
  • 逐行遍歷文本文件
  • 列表推導,字典推導,集合推導
  • 元組拆包
  • 調用函數時,使用*拆包實參

如同標題本文的標題一樣,這邊文章主要講解三個方面,可迭代對象,迭代器,生成器,下面逐個開始理解

可迭代對象

先通過下面單詞序列例子來理解:

 1 import re
 2 import reprlib
 3 
 4 
 5 RE_WORD = re.compile('\w+')
 6 
 7 
 8 class Sentence(object):
 9     def __init__(self,text):
10         self.text = text
11         self.words = RE_WORD.findall(text)
12 
13     def __getitem__(self, index):
14         return self.words[index]
15 
16     def __len__(self):
17         return len(self.words)
18 
19     def __repr__(self):
20         """
21         用於打印實例化對象時,顯示自定義內容,
22         reprlib.repr函數生成的字符換最多有30個字符,當超過怎會通過省略號顯示
23         :return: 自定義內容格式
24         """
25         return 'Sentence(%s)' % reprlib.repr(self.text)
26 
27 s = Sentence('"the time has come," the Walrus said,')
28 print(s)
29 print(type(s))
30 for word in s:
31     print(word)
32 
33 print(list(s))

上面代碼的運行結果:

首先從結果來看,我們可以看出這個類的實例是可以迭代的,
並且我們從打印print(s)的結果可以看出,顯示的也是我們定義的內容,如果我們在類中沒有通過__repr__自定義,打印結果將為:
<__main__.Sentence object at 0x102a08fd0>
同時這里的實例化對象也是一個序列,所以我們可以通過s[0]這種方式來獲取每個元素
我們都知道序列可以迭代,那么序列為啥可以迭代,繼續深入理解

序列可以迭代原因

解釋器需要迭代對象x時,會自動調用iter(x)

內置的iter函數作用:

  • 檢查對象是否實現了__iter__方法,如果實現調用它,獲取一個迭代器
  • 如果沒有實現__iter__方法,但是實現了__getitem__方法,python會創建一個迭代器,嘗試按順序(從0開始)獲取元素
  • 如果嘗試失敗,會拋出TypeError異常,通常會提示:“C object is not iterable”,其中C是目標對象所屬的類

任何python序列可以迭代的原因是,他們都實現了__getitem__方法,並且標准的序列也實現了__iter__方法。

關於如何判斷x對象是否為可迭代對象,有兩種方法:iter(x)或者isinstance(x,abc.Iterable)
那么這兩種判斷法有什么區別么?
其實從Python3.4之后建議是通過iter(x)方法來進行判斷,因為iter方法會考慮__getitem__方法,而abc.Iterable不會考慮,所以iter(x)的判斷方法更加准確

就像我最開始寫的那個例子,分別通過這兩種方式來測試,可以看出,其實這個類是可以迭代的,但是通過abc.Iterable的方式來判斷,確實不可迭代的

關於可迭代對象的一個小結:

  1. 使用iter內置函數可以獲取迭代器的對象,如果對象實現了能返回迭代器的__iter__方法,那么對象就是可迭代的
  2. 序列都可以迭代
  3. 實現了__getitem__方法,而且其參數是從零開始的索引,這種對象也可以迭代

迭代器

首先我們要明白可迭代的對象和迭代器之間的關系:
Python從可迭代的對象中獲取迭代器

一個簡單的例子,當我們循環字符串的時候,字符串就是一個可迭代的對象,背后就是有迭代器,只不過我們看不到,下面為代碼例子:

 1 # 通過for循環方式
 2 s = "ABC"
 3 for i in s:
 4     print(i)
 5 
 6 
 7 print(''.center(50, '-'))
 8 
 9 # 通過while循環方式
10 it = iter(s)
11 
12 while True:
13     try:
14         print(next(it))
15     except StopIteration:
16         del it
17         break

這兩種方式都可以獲取可迭代對象里的內容,但是while循環的方式如果不通過try/except方式獲取異常,最后就會提示StopIteration的錯誤,這是因為Python語言內部會處理for循環和其他迭代上下文(如列表推導,元組拆包等等)中的StopIteration

標准的迭代器接口有兩個方法:

  • __next__:返回下一個可用的元素,如果沒有元素了拋出StopIteration異常
  • __iter__:返回self,以便在應該使用迭代器的地方使用迭代器,例如for循環

因為迭代器只需要__next__和__iter__兩個方法,所以除了調用next()方法,以及捕獲StopIteration異常之外,沒有辦法檢查是否還有遺留元素,並且沒有辦法還原迭代器,如果想要再次迭代,就需要調用iter(...)傳入之前構建迭代器的可迭代對象

我們把剛開始寫的sentence類通過迭代器的方式來實現,要說的是這種寫法不符合python的習慣做法,這里是為了更好的理解迭代器和可迭代對象之間的重要區別

 1 import re
 2 import reprlib
 3 from collections import abc
 4 
 5 
 6 RE_WORD = re.compile('\w+')
 7 
 8 
 9 class Sentence:
10 
11     def __init__(self,text):
12         self.text = text
13         self.words = RE_WORD.findall(text)
14 
15     def __repr__(self):
16         return "Sentence(%s)" % reprlib.repr(self.text)
17 
18     def __iter__(self):
19         return SentenceIterator(self.words)
20 
21 
22 class SentenceIterator:
23 
24     def __init__(self,words):
25         self.words = words
26         self.index = 0
27 
28     def __next__(self):
29         try:
30             word = self.words[self.index]
31         except IndexError:
32             raise StopIteration()
33         self.index += 1
34         return word
35 
36     def __iter__(self):
37         return self

這樣我們就可以很清楚的明白,我們定義了一個SenteneIterator是一個迭代器,也實現了迭代器應該有的兩種方法:__next__和__iter__方法,這樣我們通過 issubclass(SentenceIterator,abc.Iterator)檢查
這里我們還能看到可迭代對象和迭代器的區別:
可迭代對象有__iter__方法,每次都實例化一個新的迭代器
迭代器要實現__next__和__iter__兩個方法,__next__用於獲取下一個元素,__iter__方法用於迭代器本身,因此迭代器可以迭代,但是可迭代對象不是迭代器

有人肯定在想在Sentence類中實現__next__方法,讓Sentence類既是可迭代對象也是自身的迭代器,但是這種想法是不對的,這是也是常見的反模式。所以可迭代對象一定不能是自身的迭代器

生成器

先通過用生成器方式替換上個例子中SentenceIterator類,例子如下:

 1 import re
 2 import reprlib
 3 
 4 
 5 RE_WORD = re.compile('\w+')
 6 
 7 
 8 class Sentence:
 9 
10     def __init__(self,text):
11         self.text = text
12         self.words = RE_WORD.findall(text)
13 
14     def __repr__(self):
15         return 'Sentence(%s)' % reprlib.repr(self.text)
16 
17     def __iter__(self):
18         for word in self.words:
19             yield word

在上面這個代碼中,我們通過yield關鍵字,這里的__iter__函數其實就是生成器函數,迭代器其實是生成器對象,每次調用__iter__方法,都會自動創建。

生成器的工作原理

Python函數定義體中有yield關鍵字,該函數就是生成器函數。

生成器函數會創建一個生成器對象,包裝生成器函數的定義體,把生成器傳給next(...)函數時,生成器函數會向前,執行函數定義體中的下一個yield語句,返回產出的值,並在函數定義體的當前位置暫停,最終,函數的定義體返回時,外層的生成器對象會拋出SotpIteration異常,這一點和迭代器協議一致。

下面是一個生成器的例子:

這里其實我們要明白進行for循環的過程其實就是在隱式的調用next()函數
當我們寫了好幾種Sentence類的時候,感覺我們通過生成器方式實現的挺簡單了,其實還有更簡單的方法的,代碼例子如下,這里的finditer函數構建了一個迭代器:

 1 import re
 2 import reprlib
 3 
 4 
 5 RE_WORD = re.compile('\w+')
 6 
 7 
 8 
 9 class Sentence:
10 
11     def __init__(self,text):
12         self.text = text
13 
14     def __repr__(self):
15         return 'Sentence(%s)' % reprlib.repr(self.text)
16 
17     def __iter__(self):
18         for match in RE_WORD.finditer(self.text):
19             yield match.group()

關於生成器表達式

生成器表達式可以理解為列表推導的惰性版本,不會直接構成列表,而是返回一個生成器,按需惰性生成元素。
關於實現Sentence,還可以通過生成器表達式。

 1 import re
 2 import reprlib
 3 
 4 
 5 RE_WORD = re.compile('\w+')
 6 
 7 
 8 class Sentence:
 9 
10     def __init__(self,text):
11         self.text = text
12 
13     def __repr__(self):
14         return 'Sentence(%s)' % reprlib.repr(self.text)
15 
16     def __iter__(self):
17         return (match.group() for match in RE_WORD.finditer(self.text))

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM