2021年10月27日 星期三

Python 學習筆記 : 可迭代物件 (iterable) 與迭代器物件 (iterator)

在 Python 中最常用的迴圈應該是 for in 語法了, 它是透過拜訪可迭代物件來走訪元素, 特別是 for i in range(n) 這樣的語句最常見, 其中的 range() 函數就是一種可迭代物件 (iterable), 它與迭代器 (iterator) 是 Python 迭代協定 (iteration protocol) 裡面所定義的兩種物件類別 : 
  • Iterable (可迭代物件) :
    實作了可傳回一個迭代器物件的 __iter__() 方法. 
  • Iterator (迭代器物件) : 
    繼承自 Iterable 並實作了 __iter__() 與 __next__() 方法的資料流物件, 前者會傳回迭代器 (iterator) 物件本身; 後者則會傳回資料流中的下一個元素 (若已無下一個元素將觸發 StopIteration 例外).
簡言之, Iterator 是 Iterable 的子類別, 故迭代器物件一定是可迭代物件, 但可迭代物件不一定是迭代器物件. 參考 :


Python 中的容器型別例如字串, 元組, 串列, 與陣列 (有序), 以及字典與集合 (無序) 等資料型態都是可迭代物件 (不是迭代器物件), 它們均繼承自 iterable 與 container 類別, 如下圖所示 : 




與迭代有關的 Python 常用內建函數有下列兩個 : 
  • iter(iterable) : 將傳入的可迭代物件轉成迭代器物件 (添加 __next__() 方法). 
  • next(iterator) : 傳回迭代器物件的下一個元素. 
注意, 由於 Python 的容器物件都不是迭代器, 而是可迭代物件, 因此不可以直接把他們傳入 next() 函數, 應該先用 iter() 轉成迭代器才可以傳給 next(), 例如 :

>>> next('abc')      # 字串是可迭代物件, 不是迭代器物件, 不可直接傳入 next()
Traceback (most recent call last):
  File "<pyshell>", line 1, in <module>
TypeError: 'str' object is not an iterator    
>>> str_itr=iter('abc')     
>>> type(str_itr)    
<class 'str_iterator'>
>>> next(str_itr)    
'a'
>>> next(str_itr)   
'b'
>>> next(str_itr)  
'c'

可見 str 物件傳給 iter() 後會傳回一個 str_iterator 類別的迭代器物件, 這個才能傳給 next(). 連續呼叫 next() 會將迭代器物件中的內容逐一傳回, 這與 for 迴圈得作用一樣. 但是在 for 迴圈中卻不需要用 iter() 先把可迭代物件轉成迭代器, 因為 for 迴圈會自動轉換, 例如 : 

>>> for i in 'abc':      # for 迴圈會自動將 'abc' 轉成迭代器 
    print(i)   
    
a
b
c

當然若要手動轉成迭代器也可以, 但那是多此一舉 :

>>> for i in iter('abc'):    
    print(i)   
    
a
b
c

檢驗一個物件是否為 Iterable 或 Iterator 物件可利用 Python 內建模組 collections 裡的兩個基礎類別 IterableIterator, 它們是 Python 所有容器物件的根類別, 需先用 import 指令匯入 :  

from collections import Iterable, Iterator

然後呼叫內建函數 isinstance() 並將這兩個類別作為第二參數傳入, 根據傳回值是 True 或 False 即可檢驗一個物件是否為一個迭代器或是一個可迭代物件 :

isinstance(my_obj, Iterable)       # 傳回 True 表示 my_obj 為可迭代物件
isinstance(my_obj, Iterator)       # 傳回 True 表示 my_obj 為迭代器物件

另外一個方法是呼叫 hasattr() 函數檢查物件是否有 '__iter__' 與 '__next__'  這兩個字串, 若只有 '__iter__'  就是 iterable 物件, 兩個都有就是 iterator 物件 : 

hasattr(my_obj, '__iter__') and not hasattr(my_obj, '__next__')    # True=可迭代物件
hasattr(my_obj, '__iter__') and hasattr(my_obj, '__next__')           # True=迭代器物件

其實迭代器只要檢查有無 __next__() 方法是否存在即可. 

例如 : 

>>> from collections import Iterable, Iterator      # 匯入迭代根類別
>>> isinstance('abc', Iterable)  
True
>>> isinstance('abc', Iterator)   
False    
>>> hasattr('abc', '__iter__')       # 字串是可迭代物件
True
>>> hasattr('abc', '__next__')      # 字串不是迭代器物件
False    

可見字串只是可迭代物件, 不是迭代器物件. 除了字串以外, 其它四個 Python 容器 (tuple, list, dict, set) 以及 range 物件都是如此.


1. range 物件 : 

呼叫 range() 函數會傳回一個可迭代的 range 物件, 但它不是迭代器, 例如 : 

>>> from collections.abc import Iterable, Iterator 
>>> type(range(5))                               # range() 傳回一個 range 物件
<class 'range'>    
>>> isinstance(range(5), Iterable)      # range 是一個可迭代物件
True
>>> isinstance(range(5), Iterator)      # range 不是一個迭代器
False
>>> for i in range(5):                           # for 迴圈會自動將可迭代物件轉成迭代器
    print(i)   
    
0
1
2
3
4
>>> next(range(5))                               # 必須是迭代器物件才能傳給 next()
Traceback (most recent call last):
  File "<pyshell>", line 1, in <module>
TypeError: 'range' object is not an iterator   
>>> n=iter(range(5))                            # 呼叫 iter() 將 range 物件轉成迭代器
>>> next(n)                                           # 呼叫 next() 會傳回迭代器的下一個元素
0
>>> next(n)   
1
>>> next(n)     
2
>>> next(n)  
3
>>> next(n)
4
>>> next(n)   
Traceback (most recent call last):
  File "<pyshell>", line 1, in <module>
StopIteration
 

2. tuple 物件 : 

元組 (tuple) 物件也是可迭代物件, 不是迭代器物件 :

>>> from collections.abc import Iterable, Iterator
>>> t=(1, 2, 3)     
>>> type(t)     
<class 'tuple'>   
>>> isinstance(t, Iterable)      
True
>>> isinstance(t, Iterator)      
False  
>>> hasattr(t, '__iter__')       
True
>>> hasattr(t, '__next__')       
False   


3. list 物件 : 

串列 (list) 物件也是可迭代物件, 不是迭代器物件 :

>>> from collections.abc import Iterable, Iterator
>>> l=[1, 2, 3]     
>>> type(l)     
<class 'list'>   
>>> isinstance(l, Iterable)      
True
>>> isinstance(l, Iterator)      
False  
>>> hasattr(l, '__iter__')       
True
>>> hasattr(l, '__next__')       
False   


4. dict 物件 : 

字典 (dict) 物件也是可迭代物件, 不是迭代器物件 :

>>> from collections.abc import Iterable, Iterator
>>> d={1: 'a', 2: 'b', 3: 'c'}     
>>> type(d)     
<class 'dict'>   
>>> isinstance(d, Iterable)      
True
>>> isinstance(d, Iterator)      
False  
>>> hasattr(d, '__iter__')       
True
>>> hasattr(d, '__next__')       
False   


5. set 物件 : 

集合 (set) 物件也是可迭代物件, 不是迭代器物件 :

>>> from collections.abc import Iterable, Iterator
>>> s={1, 2, 3}     
>>> type(s)     
<class 'set'>   
>>> isinstance(s, Iterable)      
True
>>> isinstance(s, Iterator)      
False  
>>> hasattr(s, '__iter__')       
True
>>> hasattr(s, '__next__')       
False   

參考 :


沒有留言 :