久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

一文掌握 Python 迭代器的原理

 RealPython 2021-03-18

理解迭代器是每個嚴(yán)肅的 Python 使用者學(xué)習(xí) Python 道路上的里程碑,。本文將從零開始,,一步一步帶你認(rèn)識 Python 中基于類的迭代器,。

相較于其他編程語言,,Python 的語法是優(yōu)美而清晰的。比如 for-in 循環(huán)這種極具 Python 特色的代碼,,讓你讀起來感覺就像讀一個英語句子一樣,。它很能體現(xiàn) Python 的美感。

numbers = [1, 2, 3]for n in numbers: print(n)

但是你知道這種優(yōu)雅的循環(huán)結(jié)構(gòu)背后是如何工作的嗎,?循環(huán)是如何從它所循環(huán)的那個對象中獲取獨(dú)立元素的,?你怎么才能在自己創(chuàng)建的 Python 對象上使用相同的編程風(fēng)格呢?

Python 迭代器協(xié)議為上邊這些疑問提供了答案:

Objects that support the __iter__ and __next__ dunder methods automatically work with for-in loops.

支持 __iter__ 和 __next__ 魔法方法的對象可自動適用于 for-in 循環(huán)。

就像 Python 中的裝飾器一樣,,迭代器及其相關(guān)技術(shù)乍看起來相當(dāng)神秘而復(fù)雜,。不用擔(dān)心,讓我們一步一步慢慢來認(rèn)識它,。

我們先聚焦于 Python 3 中迭代器的核心機(jī)制,,去除不必要的麻煩枝節(jié),這樣就可以在基礎(chǔ)層面清楚地看到迭代器是如何運(yùn)作的,。

下文中,,我們會編寫幾個支持迭代器協(xié)議的 Python 類。每個例子都和上邊提到的幾個 for-in 循環(huán)問題相關(guān)聯(lián),。

好,,我們現(xiàn)在進(jìn)入正題!


【永久迭代的 Python 迭代器】

我們來編寫一個可以演示 Python 迭代器協(xié)議輪廓的類,。你可能學(xué)習(xí)過其他迭代器教程,,但這里使用的例子和你在那些教程中看到的例子有些不同,或許會更有助于你的理解,。

我們將會實(shí)現(xiàn)一個名為 Repeater 的類,,這個類的對象可使用 for-in 循環(huán)來迭代。

repeater = Repeater('Hello')for item in repeater: print(item)

(代碼片段1)

誠如類的名字,,當(dāng)被迭代時,,該類的對象實(shí)例會重復(fù)返回一個單一的值。上邊這段示例代碼會不停地在控制臺窗口中打印 Hello 字符串,。

我們先來定義這個 Repeater 類:

class Repeater: def __init__(self, value): self.value = value
def __iter__(self): return RepeaterIterator(self)

這個類看起來和普通的 Python 類沒什么區(qū)別,。但是請注意它包含了一個 __iter__ 魔法方法。這個方法返回了一個 RepeaterIterator 對象,。

這個 RepeaterIterator 對象是什么,?它是一個我們需要定義的助手類,借助于 RepeaterIterator,,代碼片段一中的 repeater 對象才能在 for-in 循環(huán)中運(yùn)行起來,。

RepeaterIterator 類的定義如下:

class RepeaterIterator: def __init__(self, source): self.source = source
def __next__(self): return self.source.value

同樣,它看起來也是一個簡單的 Python 類,。不過你需要留意以下兩點(diǎn):

  1. 在 __init__ 方法中,,我們將每個 RepeaterIterator 實(shí)例鏈接到創(chuàng)建它的 Repeater 對象上。這樣,,我們就可以保存這個被迭代的 source 對象,。

  2. 在 __next__ 方法中,我們又返回了和這個 source Repeater 對象關(guān)聯(lián)的 value 值,。

Repeater 和 RepeaterIterator 兩個類共同實(shí)現(xiàn)了 Python 的迭代器協(xié)議,。其中的關(guān)鍵是 __iter__ 和 __next__ 這兩個魔法方法,正是它們才使得一個 Python 對象成為一個可迭代對象(iterable

有了這兩個類的定義,,你現(xiàn)在可以運(yùn)行代碼片段一了,。結(jié)果就是,屏幕上不停地打印 Hello 字符串:

HelloHelloHelloHelloHello...

恭喜,!你已經(jīng)實(shí)現(xiàn)了一個可以工作的 Python 迭代器,,并在 for-in 循環(huán)中使用了它。

接下來,,我們將拆解這個例子,,從而更好地理解 __iter__ 和 __next__ 是如何共同使得一個 Python 對象成為可迭代的。


【for-in 循環(huán)是如何工作的】

我們看到,,for-in 循環(huán)可以從 Repeater 對象中獲取新元素,,那么,for-in 是如何與 Repeater 對象通信的呢,?

我們對代碼片段一做些展開,,并保持同樣的運(yùn)行結(jié)果:

repeater = Repeater('Hello')iterator = repeater.__iter__()while True: item = iterator.__next__() print(item)

如你所見,for-in 就是一個簡單 while 循環(huán)的語法糖,。

  1. 它調(diào)用 repeater 對象的 __iter__ 方法獲取一個真正的 iterator 對象

  2. 然后在循環(huán)中重復(fù)調(diào)用 iterator 對象的 __next__ 方法,,從中獲取下一個值

如果你從事過數(shù)據(jù)庫應(yīng)用開發(fā),使用過數(shù)據(jù)庫游標(biāo)(cursor),,那對此模型應(yīng)該就不陌生了:先初始化一個游標(biāo),,將其準(zhǔn)備(prepare)好用來讀取數(shù)據(jù),然后從中獲取數(shù)據(jù)(fetch)并保存到本地變量中,。


由于只占用了一個 value 的空間,,這種方式具備很高的內(nèi)存效率。Repeater 類可以用作一個包含元素的無限序列,,而這無法通過 Python 列表來實(shí)現(xiàn),,我們不可能創(chuàng)建一個包含無限多元素的 list 對象。這是迭代器的一個強(qiáng)大的功能,。

使用迭代器的另一個好處是,,它提供了一種抽象語義。迭代器可用于多種容器,,它為這些容器提供了統(tǒng)一的接口,,你不需要關(guān)心容器的內(nèi)部結(jié)構(gòu)就可以從中提取每個元素

無論你使用列表,、字典,、無限序列還是別的序列類型,,它們都只是代表了一種實(shí)現(xiàn)上的細(xì)節(jié),。而你可以通過迭代器以相同的方式來遍歷它們。


我們看到,for-in 循環(huán)并無特別之處,,它只是在正確的時間調(diào)用了正確的魔法方法而已,。

實(shí)際上,我們也可以在 Python 解釋器命令窗口中手動模擬循環(huán)是如何使用迭代器協(xié)議的,。

>>> repeater = Repeater('Hello')>>> iterator = iter(repeater)>>> next(iterator)'Hello'>>> next(iterator)'Hello'>>> next(iterator)'Hello'...

每調(diào)用一次 next(),,iterator 就會輸出一個 Hello。

這里,,我們使用 Python 內(nèi)置的 iter() 和 next() 函數(shù)來代替對象的 __iter__ 和 __next__ 方法,。這些內(nèi)置函數(shù)其實(shí)也是調(diào)用了對應(yīng)的魔法函數(shù),只不過它們?yōu)榈鲄f(xié)議披上了一件干凈的外衣,,使代碼看起來更漂亮更簡單,。

通常,使用內(nèi)置函數(shù)比直接訪問魔法方法要好,,因?yàn)榇a的可讀性更強(qiáng)一些,。


【一個更簡單的迭代器類】

上邊的例子中,我們使用兩個獨(dú)立的類來實(shí)現(xiàn)迭代器協(xié)議,,每個類承擔(dān)協(xié)議的一部分職責(zé),。而很多時候,這兩項(xiàng)職責(zé)可以由一個類來承擔(dān),,從而減少代碼量,。

我們現(xiàn)在就來簡化之前實(shí)現(xiàn)迭代器協(xié)議的方法。

還記得我們?yōu)槭裁匆x RepeaterIterator 這個類嗎,?我們用它來定義獲取元素的 __next__ 方法,。而實(shí)際上,在什么地方定義 __next__ 方法是無關(guān)緊要的,。

迭代器協(xié)議關(guān)心的是,,__iter__ 方法必須返回一個提供了 __next__ 方法的對象

我們再審視一下 RepeaterIterator 這個類,,它其實(shí)并沒有做太多的工作,,僅僅返回了 source 的成員變量 value。我們是不是可以省去這個類,,將其功能融入 Repeater 類中,?可以試一下。

我們可以這樣來實(shí)現(xiàn)新的并且更簡單的迭代器類,。

class Repeater: def __init__(self, value): self.value = value
def __iter__(self): return self
def __next__(self): return self.value

解釋一下:

  1. __iter__ 實(shí)現(xiàn)迭代器協(xié)議的第一項(xiàng)職責(zé),,將 Repeater 自身返回

  2. Repeater 自己定義了 __next__ 方法,每次都返回成員變量 value,,從而實(shí)現(xiàn)迭代器協(xié)議的第二項(xiàng)職責(zé)

使用代碼片段一來測試這個 Repeater 類,,同樣會不停地輸出 Hello,。

通過兩個類來實(shí)現(xiàn)迭代器協(xié)議,我們能很好地理解迭代器協(xié)議的底層原則,;而使用一個類則可以提升開發(fā)效率,,非常有意義。


【迭代器不能永遠(yuǎn)迭代下去】

我們已經(jīng)理解了迭代器的工作原理,,但是我們目前實(shí)現(xiàn)的迭代器僅僅可以無限迭代下去,,而這并非 Python 中迭代器的主要使用場景。

在本文的開頭,,我們舉了個簡單 for-in 循環(huán)的例子作為引子:

numbers = [1, 2, 3]for n in numbers: print(n)

這才是更加普遍的應(yīng)用場景:輸出有限的元素,,然后終止。

我們該如何實(shí)現(xiàn)這樣的迭代器呢,?迭代器如何給出元素耗盡迭代結(jié)束的信號呢,?

或許你會想到,可以在迭代結(jié)束時返回 None,。聽起來是個不錯的主意,,但是并非適合所有情況,某些場景可能不接受 None 作為合法值,。

我們可以看一下 Python 中的其他迭代器是如何處理這一問題的,。我們先創(chuàng)建一個簡單的容器:一個包含若干元素的 list。然后迭代這個 list,,直到元素耗盡,,看看會發(fā)生什么情況。

>>> my_list = [1, 2, 3]>>> iterator = iter(my_list)
>>> next(iterator)1>>> next(iterator)2>>> next(iterator)3

注意,,此時我們已消費(fèi)了 list 中的所有元素,。如果繼續(xù)調(diào)用 next(),會發(fā)生什么,?

>>> next(iterator)Traceback (most recent call last): File "<stdin>", line 1, in <module>StopIteration

報異常了,!

沒錯:迭代器就是使用異常來改變控制流程的。為了提示迭代的結(jié)束,,Python 迭代器簡單地拋出一個內(nèi)置的 StopIteration 異常,。

Python 迭代器正常情況下不能被重置。一旦耗盡,,每次在迭代器上調(diào)用 next() ,,迭代器都會拋出 StopIteration 異常。如果想重新執(zhí)行迭代,,你需要通過 iter() 函數(shù)來獲取一個全新的迭代器對象,。


現(xiàn)在我們已經(jīng)弄清楚了如何編寫一個非無限迭代的迭代器類了,。我們將其命名為 BoundedRepeater,,它可以在執(zhí)行有限次重復(fù)后停止迭代,。

class BoundedRepeater: def __init__(self, value, max_repeats): self.value = value self.max_repeats = max_repeats self.count = 0
def __iter__(self): return self
def __next__(self): if self.count >= self.max_repeats: raise StopIteration self.count += 1 return self.value

BoundedRepeater 可以為我們提供想要的結(jié)果。當(dāng)?shù)螖?shù)超過 max_repeats 指定的值時,,迭代就會停止,。

>>> repeater = BoundedRepeater('Hello', 3)>>> for item in repeater: print(item)HelloHelloHello

如果我們移除上邊這個 for-in 循環(huán)中的語法糖,它可以重寫為:

repeater = BoundedRepeater('Hello', 3)iterator = iter(repeater)while True: try: item = next(iterator) except StopIteration: break print(item)

我們需要在每次調(diào)用 next() 時手動檢查是否有異常拋出,。for-in 循環(huán)替我們做了這項(xiàng)工作,,讓代碼變得更易讀和更易維護(hù)。這也是 Python 迭代器為何如此強(qiáng)大的另一個原因,。


【兼容 Python 2.x 的迭代器】

上文中用到的例子都是使用 Python 3 編寫的,。如果你使用 Python 2 來實(shí)現(xiàn)基于類的迭代器,需要注意一個細(xì)小但重要的區(qū)別:

  • Python 3 中,,從迭代器獲取下一個值的方法是:__next__

  • Python 2 中,,這個方法叫做:next (沒有下劃線)

這個命名上的區(qū)別可能會導(dǎo)致代碼的不兼容。

解決辦法也很簡單,,讓迭代器類同時支持這兩個方法,。

以那個無限序列類為例:

class InfiniteRepeater(object): def __init__(self, value): self.value = value
def __iter__(self): return self
def __next__(self): return self.value
# Python 2 compatibility: def next(self): return self.__next__()

我們?yōu)槠湓黾恿艘粋€ next() 方法,該方法僅僅調(diào)用 __next__() 就可以了,。

還有一個變動,,我們將類的定義修改為繼承自 object 類,,以使其符合 Python 2 中的新式類定義方法。這和迭代器無關(guān),,只是一個良好的習(xí)慣,。


【結(jié)語】

  • 迭代器為 Python 對象提供了一個內(nèi)存效率高的序列接口,,使用起來也更具 Python 風(fēng)格。

  • Python 對象可通過實(shí)現(xiàn) __iter__ 和 __next__ 魔法方法的方式來支持迭代。

  • 基于類的迭代器是 Python 中的一種編寫可迭代對象的方法,,除此之外,還可以考慮使用生成器和生成器表達(dá)式,。


【近期熱門文章】

  1. 從 Python 列表的特性來探究其底層實(shí)現(xiàn)機(jī)制

  2. 從 Python 源碼來分析列表的 resize 機(jī)制

  3. 列表推導(dǎo)式:簡潔高效更具 Python 風(fēng)格的列表創(chuàng)建方法

  4. Python 列表的應(yīng)用場景有哪些,?你使用對了嗎,?

    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多