在 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 裡的兩個基礎類別 Iterable 與 Iterator, 它們是 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
參考 :
沒有留言:
張貼留言