跟你深入剖析可迭代物件和迭代器的區別與聯絡

WeifaGan發表於2020-05-12

導語

可迭代物件和迭代器是經常碰到但又很容易混淆的兩個概念,所以今天小編跟大家深入剖析一下可迭代物件和迭代器的區別。認真看完本文,你將收穫:

  • 理解什麼是可迭代物件
  • 理解檢查可迭代物件的方法 
  • 理解什麼是迭代器
  • 可迭代物件和迭代器的關係

事不宜遲,我們馬上開始吧!

可迭代物件

要理解可迭代物件,那首先要搞清楚迭代的概念。關於迭代,維基百科是這樣子定義的:

迭代是重複反饋過程的活動,其目的通常是為了接近併到達所需的目標或結果。每一次對過程的重複被稱為一次“迭代”,而每一次迭代得到的結果會被用來作為下一次迭代的初始值。

從這個定義中,我們大概可以知道迭代是對某一個過程的重複。其實在程式中,迭代也是類似的,它是一種遍歷集合元素的方式,請看下面的示例1。

# 示例1
for i in [1,2,3]:
    print(i)

輸出結果:

1
2
3

在示例1中,直譯器重複地從列表中取出元素並列印,直到遍歷結束為止,這就是一個迭代的過程。可見,可迭代物件可以在for迴圈中遍歷元素。

那什麼樣的物件才是一個可迭代物件?事實上,只要實現 iter 方法或者實現 __ getitem __方法而且其引數從0開始索引,那麼該物件就是可迭代物件,請看示例2。

#示例2
class Vector(object):
    def __init__(self,components):
        self.components = list(components)
        
    def __iter__(self):
        return iter(self.components)

V1 = Vector([1,2,3])
for i in V1:
    print(i)

輸出結果:

1
2
3

從示例2中可見,Vector類實現了 __ iter __方法,直譯器可以從Vector類物件中重複地取出元素並列印。如果要檢查某一個物件是否為可迭代物件,其實可以使用isinstance( )函式,該函式用於判斷物件是否為某一型別,但是用這個函式判斷不一定準確(原因後面會說到)。

from collections import Iterable
print(isinstance(V1,Iterable))#True

如果我們只是實現了__ getitem __ ()方法,情況又會怎麼樣呢?請看示例3。

# 示例3
from collections import Iterable
class Vector(object):
    def __init__(self,components):
        self.components = list(components)
    
    def __getitem__(self,index):
        return self.components[index]
    
V1 = Vector([1,2,3])
for i in V1:
    print(i)
print(isinstance(V1,Iterable))

輸出結果:

1
2
3
Flase

從示例3中可看到,Vector實現了__ getitem__方法,直譯器可以對V1進行迭代並列印元素,但是!使用isinstance()判斷時,返回的結果居然是False。明明可以使用for迴圈來迭代元素,為什麼是判斷是Flase呢?事實上,如果可迭代物件只是實現了__ getitem __ 的話,abc.Iterable是不考慮該方法的,這便導致了isinstance()判斷不準確。更準確的方法應該是呼叫iter()函式,如果該物件不可迭代,就會丟擲TypeError的錯誤。我們嘗試使用iter()來判斷一下。

# 示例4
print(iter(V1))
#<iterator object at 0x000001B262E35518>

#去掉__getitem__方法後
print(iter(V1))
#TypeError: 'Vector' object is not iterable

從示例4中可以看到,對於可迭代物件,iter()會返回< iterator object at xxxx >。當去掉__ getitem __ ()方法後再檢查時,便丟擲TypeError錯誤。iter()函式用於生成一個迭代器,也就是說可以返回一個迭代器的就是一個可迭代物件。

該物件之所以能迭代,是因為實現了__iter__()方法。當使用for迴圈時候,直譯器會檢查物件是否有__ iter__ ()方法,有的話就是呼叫它來獲取一個迭代器。所以沒有__ iter__ ()方法但實現了__ getitem __ (),直譯器會建立一個迭代器,嘗試從0開始按順序遍歷元素。如果嘗試失敗,Python便會丟擲TypeError錯誤。

那麼Python內建型別中究竟有哪些可迭代物件呢?我們一起盤點一下吧。

  • list
  • dict
  • tuple
  • set
  • string

其實盤點出來的都是序列,所以說任何序列都是可迭代的物件, 其原因在於他們至少都會實現__ getittem __ 方法(序列都可以通過索引獲取元素)。

迭代器

在介紹可迭代物件時候說到,當使用for迴圈時候,直譯器會檢查物件是否有__ iter __ ()方法,有的話就是呼叫它來獲取一個迭代器。那麼究竟什麼是迭代器呢?

其實迭代器是實現了__iter__方法和__next__方法的物件。__ iter__ 方法用於返回迭代器本身,而__ next __ 用於返回下一個元素。我們自定義一個迭代器,以斐波那契數列為例說明一下其內部的執行情況,看示例5。

# 示例5
import itertools
class Fib:
    def __init__(self):
        self.pre = 0
        self.cur = 1

    def __iter__(self):
        return self
    
    def __next__(self):
        p = self.cur
        self.cur += self.pre
        self.pre = p
        return p

f = Fib()
a = list(itertools.islice(f,0,10))
print(a)
#[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

從示例5中__ iter__ ()返回了迭代器物件本身,以便在使用可迭代物件的地方(如for迴圈中)使用迭代器,而__ next __ ()則通過計算返回下一個元素。我們再一起看看下面的示例6。

#示例6
>>>s = 'ABCD'
>>>it = iter(s)
>>>while True:print(next(it))
A
B
C
D
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-4-d09d5cde4495> in <module>()
----> 1 while True:print(next(it))

StopIteration:
>>> list(it)
[]
>>>list(iter(s))
['A','B','C','D']

在示例6中先定義一個字串的可迭代物件,通過iter()函式返回一個迭代器(會自動呼叫物件的__ iter__ 方法),然後在迴圈中通過next()取值並列印(會自動呼叫物件 __ next __()方法),通過next()方法一個個地遍歷可迭代物件中的元素,當遍歷結束,便會丟擲StopIteratioin異常,這時迭代器也沒用了,如果要再次迭代,就要使用iter()函式重新構建迭代器。

通過示例5和示例6,我們可知道迭代器是一個可以記住遍歷位置的物件,其內部有一個狀態用於記錄迭代所在的位置,以便下次迭代時候能取出正確的元素。迭代器就像一個懶人一樣,當你需要資料時候才會返回給你,否則就在等待下一次的呼叫。

如果要檢查某個物件是否為迭代器,最好的方式是使用isinstance( )函式,見示例7。

# 示例7
from collections import Iterator 
f = Fib()
print(isinstance(f,Iterator)) #True

好了,說了那麼多,究竟迭代器哪些用處呢?其實在Python語言內部,迭代器用於支援:

  • for 迴圈
  • 構建和擴充套件集合型別
  • 逐行遍歷文字檔案
  • 列表推導、字典推導和集合推導
  • 元組拆包
  • 呼叫函式時,使用*拆包

可迭代物件和迭代器的關係

  • 可迭代物件不一定是迭代器,迭代器一定是可迭代物件。因為迭代器一定會實現__ iter__方法,而可迭代物件儘管實現了__ iter __ 也不一定實現__next__方法。
  • Python 從可迭代物件中獲取迭代器,根據示例6的例子,我們知道先是使用iter()函式在迭代物件中獲取迭代器,然後使用next()來獲取下一個元素,關係如下圖所示。

總結

  • 可迭代物件實現了__iter__ 方法或者實現 __ getitem__方法而且其引數從0開始索引。
  • 使用iter()函式判斷可迭代物件更準確
  • 任何序列都是可迭代物件
  • 迭代器物件實現了__ iter __和__next __方法。
  • 迭代器是一個可以記住遍歷位置的物件,其內部有一個狀態用於記錄迭代所在的位置,以便下次迭代時候能取出正確的元素
  • 檢查物件是否為迭代器最好的方式是呼叫isinstance()方法。

以上就是小編今天跟大家分享的內容了,如果有什麼疑問記得聯絡小編哦~

公眾號:CVpython,專注於分享Python和計算機視覺,我們堅持原創,不定期更新,希望文章對你有幫助,快點掃碼關注吧。

相關文章