來源:北大青鳥總部 2023年01月13日 11:01
說到Python編程語言,最令人印象深刻的應該就是它的易用性了。為了提供易用性,語言中封裝了大量的常用數據結構、算法和類庫,并創建了不少
與其他語言不同的概念。其中,大部分概念都非常容易理解。然而,仍有些概念比較相似,常常使初學者混淆,比如迭代器和可迭代對象。
有編程經驗的開發者都知道,迭代(或稱循環)是處理大量數據時非常常用的手段。
查看下面一個常規的類定義:
class SimpleClass1:
pass
simple1 = SimpleClass1()
如果從simple對象獲取數據:
next(simple1)
將會報錯“TypeError: 'SimpleClass1' object is not an iterator”,這是因為simple1對象不是一個迭代器。
下面介紹Python中的可迭代協議。
如果要使一個對象成為一個迭代器,需要:
實現無參數的“__next__”方法,返回下一個數據;
當沒有下一個數據時,拋出一個特殊的異常StopIteration。
那么,重新實現SimpleClass,如下:
class SimpleClass2:
def __init__(self, name):
self.name = name
self.current = 0
def __next__(self):
if self.current >= len(self.name):
raise StopIteration
nextval = self.name[self.current]
self.current += 1
return nextval
simple2 = SimpleClass2('abc')
重新使用next函數就可以獲取數據了:
next(simple2) # 返回a
next(simple2) # 返回b
next(simple2) # 返回c
next(simple2) # 拋出異常 StopIteration
如上所示,迭代器可以成功返回數據,如預期那樣。但是每次都使用next函數獲取數據還是比較麻煩,更不用說還要去處理異常。
如果在開發中,對象能夠直接支持for循環來進行遍歷,并且自動處理StopIteration異常,那么實際開發工作將會簡單許多。
于是Python中引入了可迭代對象的概念,可迭代對象就是能夠支持使用iter來獲取迭代器的對象。我們可以在類中實現__iter__方法來支持iter函數:
class SimpleClass3:
def __init__(self, name):
self.name = name
self.current = 0
def __next__(self):
if self.current >= len(self.name):
raise StopIteration
nextval = self.name[self.current]
self.current += 1
return nextval
def __iter__(self):
print('__iter__方法被調用')
return self
simple3 = SimpleClass3('abc')
使用for循環打印元素:
for item in simple3:
print(item)
將會順序輸出 a, b, c三個元素,for循環語句會自動調用iter獲取此可迭代對象的迭代器,并自動處理異常。
以上就是Python中的可迭代協議。下面使用該協議仿照系統內置range實現一個簡化版本的類SimpleRange,它支持返回從0到n(不包括)的整數值。
class _SimpleRange:
def __init__(self, n):
self.n = n
self.current = 0
def __iter__(self):
return self
def __next__(self):
"""支持獲取下一個元素"""
if self.current >= self.n:
raise StopIteration # 當沒有下一個元素時拋出異常
next_val = self.current # 保存當前值以便返回
self.current += 1
return next_val
class SimpleRange:
"""簡化版本的range"""
def __init__(self, n):
"""初始化對象"""
self.n = n
def __iter__(self):
"""支持返回迭代器"""
return _SimpleRange(self.n)
simple_range = SimpleRange(10)
r = range(10)
assert list(simple_range) == list(r)
assert list(simple_range) == list(r) # 該斷言會成功通過
上面的代碼中,_SimpleRange實現了__next__方法,所以其對象是一個迭代器。而SimpleRange實現了_iter__方法,并且在其中返回一個新的_SimpleRange對象。SimpleRange是一個可迭代對象。
需要注意的是,在SimpleRange對象中每次調用iter都會返回一個全新的迭代器(即_SimpleRange對象),這就是上面代碼中,第二個斷言能夠通過的原因。
下面看第二個例子,定義一個列表如下:
lst = [1, 2, 3, 4, 5, 6, 7, 8, 9]
我們知道,lst是可迭代對象,所以可以使用iter函數獲取其迭代器iter(lst)。而如果將同一個迭代器放入zip函數,可以同時分別從
同一個迭代器獲取數據,即:
lst_iter = iter(lst)
assert list(zip(lst_iter, lst_iter, lst_iter)) == [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
將上面的代碼組合在一起,配合拆包則可以使用代碼:
list(zip(*[iter(lst)]*3))
將列表 [1, 2, 3, 4, 5, 6, 7, 8, 9],轉換為 [(1, 2, 3), (4, 5, 6), (7, 8, 9)]。
除了標準的實現可迭代的方法(即實現__iter__方法)外,如果一個類實現了__getitem__方法,并且其索引是從0開始的整數,則
其對象也是可迭代對象。如:
class SimpleClass4:
def __init__(self, n):
self.n = n
def __getitem__(self, idx):
if idx < self.n:
return idx
raise StopIteration
可迭代對象就是可以用來拿到迭代器的對象,而迭代器可以用來獲取下一個數據。
可迭代對象實現了返回迭代器的__iter__方法或者使用從0開始的整數索引的__getitem__方法;迭代器實現了獲取下一個元素的__next__方法,當沒有下一個元素時,迭代器會拋出一個特殊的異常StopIteration。
Python中的許多結構內置支持可迭代協議,會自動處理StopIteration異常,如for循環、拆包等。