我們都知道,序列可以迭代。但是,你知道為什麼嗎? 本文來探討一下迭代背後的原理。
序列可以迭代的原因:iter 函式。直譯器需要迭代物件 x 時,會自動呼叫 iter(x)。內建的 iter 函式有以下作用:
(1) 檢查物件是否實現了 iter 方法,如果實現了就呼叫它,獲取一個迭代器。
(2) 如果沒有實現 iter 方法,但是實現了 getitem 方法,而且其引數是從零開始的索引,Python 會建立一個迭代器,嘗試按順序(從索引 0 開始)獲取元素。
(3) 如果前面兩步都失敗,Python 丟擲 TypeError 異常,通常會提示“C objectis not iterable”(C 物件不可迭代),其中 C 是標的物件所屬的類。
由此我們可以明確知道什麼是 可迭代的物件: 使用 iter 內建函式可以獲取迭代器的物件。即要麼物件實現了能傳回迭代器的 iter 方法,要麼物件實現了 getitem 方法,而且其引數是從零開始的索引。
下麵看一個實現了getitem方法的例子:
class Eg1:
def __init__(self, text):
self.text = text
self.sub_text = text.split(' ')
def __getitem__(self, index):
return self.sub_text[index]
o1 = Eg1('Hello, the wonderful new world!')
for i in o1:
print(i)
輸出結果:
Hello,
the
wonderful
new
world!
我們建立了一個類Eg1,並且為這個類實現了 getitem 方法, 它的實體化物件o1 就是可迭代物件。
下麵我們看一個實現 iter 方法的例子,因為用到了迭代器,所以在此我們必須在明確一下迭代器的用法。 標準的迭代器介面有兩個方法:
__next__
傳回下一個可用的元素,如果沒有元素了,丟擲 StopIteration異常。
__iter__
傳回 self,以便在應該使用可迭代物件的地方使用迭代器,例如在 for 迴圈中。
class Eg2:
def __init__(self, text):
self.text = text
self.sub_text = text.split(' ')
def __iter__(self):
return Eg2Iterator(self.sub_text)
class Eg2Iterator:
def __init__(self, sub_text):
self.sub_text = sub_text
self.index = 0
def __next__(self):
try:
subtext = self.sub_text[self.index]
except IndexError:
raise StopIteration()
self.index += 1
return subtext
def __iter__(self):
return self
我們建立了Eg2類,併為它實現了 iter 方法,此方法傳回一個迭代器Eg2Iterator。 Eg2Iterator 實現了我們之前所說的next和iter方法。 實體化物件,並迴圈輸出:
o2 = Eg2('Hello, the wonderful new world!')
for i in o2:
print(i)
Hello,
the
wonderful
new
world!
可見,和o1是一樣的。
我們透過兩種方法實現了一個自己的可迭代物件,再此過程中我們要明確可迭代的物件和迭代器之間的關係:
Python 從可迭代的物件中獲取迭代器。
iter方法從我們自己建立的迭代器類中獲取迭代器,而getitem方法是python內部自動建立迭代器。
至此,我們明白瞭如何正確地實現可迭代物件,並且引出了怎樣實現迭代器,但是使用迭代器方法(即上面的例子2)的程式碼量有點大,下麵我們來瞭解一下如何使用更符合 Python 習慣的方式實現 Eg2類。
class Eg3:
def __init__(self, text):
self.text = text
self.sub_text = text.split(' ')
def __iter__(self):
for item in self.sub_text:
yield item
哦了!就這麼簡單優雅!不用再單獨定義一個迭代器類!
這裡我們使用了yield 關鍵字, 只要 Python 函式的定義體中有 yield 關鍵字,該函式就是生成器函式。呼叫生成器函式時,會傳回一個生成器物件。也就是說,生成器函式是生成器工廠。 當然,例子3的程式碼還可以使用yield from進一步簡化:
class Eg4:
def __init__(self, text):
self.text = text
self.sub_text = text.split(' ')
def __iter__(self):
yield from self.sub_text
o4 = Eg4('Hello, the wonderful new world!')
for i in o4:
print(i)
Hello,
the
wonderful
new
world!
到這裡我們明白了 可迭代物件 和 迭代器,還引申出了生成器,但還有一點沒有提,那就是生成器運算式。
使用生成器運算式例子4的程式碼可以修改為:
class Eg5:
def __init__(self, text):
self.text = text
self.sub_text = text.split(' ')
def __iter__(self):
return (item for item in self.sub_text)
在python中,所有生成器都是迭代器。
最後,總結一下:
(1)什麼是可迭代物件? 可迭代物件要麼實現了能傳回迭代器的 iter 方法,要麼實現了 getitem 方法而且其引數是從零開始的索引。
(2)什麼是迭代器? 迭代器是這樣的物件:實現了無引數的 next 方法,傳回下一個元素,如果沒有元素了,那麼丟擲 StopIteration 異常;並且實現iter 方法,傳回迭代器本身。
(3)什麼是生成器? 生成器是帶有 yield 關鍵字的函式。呼叫生成器函式時,會傳回一個生成器物件。
(4)什麼是生成器運算式? 生成器運算式是建立生成器的簡潔句法,這樣無需先定義函式再呼叫。
《Python人工智慧和全棧開發》2018年07月23日即將在北京開課,120天衝擊Python年薪30萬,改變速約~~~~
*宣告:推送內容及圖片來源於網路,部分內容會有所改動,版權歸原作者所有,如來源資訊有誤或侵犯權益,請聯絡我們刪除或授權事宜。
- END -
更多Python好文請點選【閱讀原文】哦
↓↓↓