首頁 後端開發 Python教學 Python中的迭代器與生成器進階用法

Python中的迭代器與生成器進階用法

Mar 01, 2017 pm 02:09 PM
python 產生器 迭代器

迭代器

迭代器是依附於迭代協定的物件-基本意義它有一個next方法(method),當呼叫時,傳回序列中的下一個項目。當無項目可返回時,引發(raise)StopIteration異常。

迭代物件允許一次循環。它保留單次迭代的狀態(位置),或從另一個角度講,每次循環序列都需要一個迭代物件。這意味我們可以同時迭代同一個序列不止一次。將迭代邏輯和序列分開使我們有更多的迭代方式。

呼叫一個容器(container)的__iter__方法建立迭代物件是掌握迭代器最直接的方式。 iter函數為我們節省一些按鍵。

>>> nums = [1,2,3]   # note that ... varies: these are different objects
>>> iter(nums)              
<listiterator object at ...>
>>> nums.__iter__()           
<listiterator object at ...>
>>> nums.__reversed__()         
<listreverseiterator object at ...>

>>> it = iter(nums)
>>> next(it)      # next(obj) simply calls obj.next()
1
>>> it.next()
2
>>> next(it)
3
>>> next(it)
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
StopIteration
登入後複製

當在循環中使用時,StopIteration被接受並停止循環。但透過明確引發(invocation),我們看到一旦迭代器元素被耗盡,訪問它將引發異常。

使用for...in迴圈也使用__iter__方法。這允許我們透明地開始對一個序列迭代。但是如果我們已經有一個迭代器,我們想在for迴圈中能同樣地使用它們。為了實現這一點,迭代器除了next還有一個方法__iter__來傳回迭代器本身(self)。

Python中對迭代器的支援無所不在:標準庫中的所有序列和無序容器都支援。這個概念也被拓展到其它東西:例如file物件支援行的迭代。

>>> f = open(&#39;/etc/fstab&#39;)
>>> f is f.__iter__()
True
登入後複製

file本身就是一個迭代器,它的__iter__方法並沒有建立一個單獨的物件:僅僅單執行緒的順序讀取被允許。

產生表達式
第二種建立迭代物件的方式是透過 產生表達式(generator expression) ,清單推導(list comprehension)的基礎。為了增加清晰度,產生表達式總是封裝在括號或表達式中。如果使用圓括號,則建立了一個產生迭代器(generator iterator)。如果是方括號,這個過程被‘短路'我們獲得一個列表list。

>>> (i for i in nums)          
<generator object <genexpr> at 0x...>
>>> [i for i in nums]
[1, 2, 3]
>>> list(i for i in nums)
[1, 2, 3]
登入後複製

在Python 2.7和 3.x中列表表達式語法被擴展到 字典和集合表達式。一個集合set當產生表達式是被大括號封裝時被建立。一個字典dict在表達式包含key:value形式的鍵值對時被創建:

>>> {i for i in range(3)}  
set([0, 1, 2])
>>> {i:i**2 for i in range(3)}  
{0: 0, 1: 1, 2: 4}
登入後複製

如果您不幸身陷古老的Python版本中,這個語法有點糟糕:

>>> set(i for i in &#39;abc&#39;)
set([&#39;a&#39;, &#39;c&#39;, &#39;b&#39;])
>>> dict((i, ord(i)) for i in &#39;abc&#39;)
{&#39;a&#39;: 97, &#39;c&#39;: 99, &#39;b&#39;: 98}
登入後複製

產生表達式相當簡單,不用多說。只有一個陷阱值得提及:在版本小於3的Python中索引變數(i)會洩漏。

產生器

生成器是產生一列結果而不是單一值的函數。

第三種建立迭代物件的方式是呼叫生成器函數。一個 生成器(generator) 是包含關鍵字yield的函數。值得注意,光是這個關鍵字的出現完全改變了函數的本質:yield語句不必引發(invoke),甚至不必可接觸。但讓函數變成了生成器。當一個函數被呼叫時,其中的指令被執行。而當一個生成器被呼叫時,執行在其中第一條指令之前停止。生成器的呼叫創建依附於迭代協定的生成器物件。就像常規函數一樣,允許並發和遞歸呼叫。
當next被呼叫時,函數執行到第一個yield。每次遇到yield語句得到一個作為next傳回的值,在yield語句執行後,函數的執行又被停止。

>>> def f():
...  yield 1
...  yield 2
>>> f()                  
<generator object f at 0x...>
>>> gen = f()
>>> gen.next()
1
>>> gen.next()
2
>>> gen.next()
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
StopIteration
登入後複製

讓我們遍歷單一生成器函數呼叫的整個歷程。

>>> def f():
...  print("-- start --")
...  yield 3
...  print("-- middle --")
...  yield 4
...  print("-- finished --")
>>> gen = f()
>>> next(gen)
-- start --
3
>>> next(gen)
-- middle --
4
>>> next(gen)              
-- finished --
Traceback (most recent call last):
 ...
StopIteration
登入後複製

比較常規函數中執行f()立即讓print執行,gen不執行任何函數體中語句就被賦值。只有當gen.next()被next調用,直到第一個yield部分的語句才被執行。第二個語句印製-- middle --並在遇到第二個yield時停止執行。第三個next列印-- finished --並且到函數末尾,因為沒有yield,引發了異常。

當函數yield之後控制權回傳給呼叫者後發生了什麼事?每個生成器的狀態被儲存在生成器物件中。從這點看生成器函數,好像它是運行在單獨的線程,但這只是假象:執行是嚴格單線程的,但解釋器保留和儲存在下一個值請求之間的狀態。

為何生成器有用?正如關於迭代器這部分所強調的,生成器函數只是創建迭代物件的另一種方式。一切能被yield語句完成的東西也能被next方法完成。然而,使用函數讓解釋器魔力般地創建迭代器有優勢。一個函數可以比需要next和__iter__方法的類別定義短很多。更重要的是,相較於必須對迭代物件在連續next呼叫之間傳遞的實例(instance)屬性來說,生成器的作者能更簡單的理解局限在局部變數中的語句。

还有问题是为何迭代器有用?当一个迭代器用来驱动循环,循环变得简单。迭代器代码初始化状态,决定是否循环结束,并且找到下一个被提取到不同地方的值。这凸显了循环体——最值得关注的部分。除此之外,可以在其它地方重用迭代器代码。

双向通信
每个yield语句将一个值传递给调用者。这就是为何PEP 255引入生成器(在Python2.2中实现)。但是相反方向的通信也很有用。一个明显的方式是一些外部(extern)语句,或者全局变量或共享可变对象。通过将先前无聊的yield语句变成表达式,直接通信因PEP 342成为现实(在2.5中实现)。当生成器在yield语句之后恢复执行时,调用者可以对生成器对象调用一个方法,或者传递一个值 给 生成器,然后通过yield语句返回,或者通过一个不同的方法向生成器注入异常。

第一个新方法是send(value),类似于next(),但是将value传递进作为yield表达式值的生成器中。事实上,g.next()和g.send(None)是等效的。

第二个新方法是throw(type, value=None, traceback=None),等效于在yield语句处

raise type, value, traceback
登入後複製

不像raise(从执行点立即引发异常),throw()首先恢复生成器,然后仅仅引发异常。选用单次throw就是因为它意味着把异常放到其它位置,并且在其它语言中与异常有关。

当生成器中的异常被引发时发生什么?它可以或者显式引发,当执行某些语句时可以通过throw()方法注入到yield语句中。任一情况中,异常都以标准方式传播:它可以被except和finally捕获,或者造成生成器的中止并传递给调用者。

因完整性缘故,值得提及生成器迭代器也有close()方法,该方法被用来让本可以提供更多值的生成器立即中止。它用生成器的__del__方法销毁保留生成器状态的对象。

让我们定义一个只打印出通过send和throw方法所传递东西的生成器。

>>> import itertools
>>> def g():
...   print &#39;--start--&#39;
...   for i in itertools.count():
...     print &#39;--yielding %i--&#39; % i
...     try:
...       ans = yield i
...     except GeneratorExit:
...       print &#39;--closing--&#39;
...       raise
...     except Exception as e:
...       print &#39;--yield raised %r--&#39; % e
...     else:
...       print &#39;--yield returned %s--&#39; % ans

>>> it = g()
>>> next(it)
--start--
--yielding 0--
0
>>> it.send(11)
--yield returned 11--
--yielding 1--
1
>>> it.throw(IndexError)
--yield raised IndexError()--
--yielding 2--
2
>>> it.close()
--closing--
登入後複製

注意: next还是__next__?

在Python 2.x中,接受下一个值的迭代器方法是next,它通过全局函数next显式调用,意即它应该调用__next__。就像全局函数iter调用__iter__。这种不一致在Python 3.x中被修复,it.next变成了it.__next__。对于其它生成器方法——send和throw情况更加复杂,因为它们不被解释器隐式调用。然而,有建议语法扩展让continue带一个将被传递给循环迭代器中send的参数。如果这个扩展被接受,可能gen.send会变成gen.__send__。最后一个生成器方法close显然被不正确的命名了,因为它已经被隐式调用。

链式生成器
注意: 这是PEP 380的预览(还未被实现,但已经被Python3.3接受)

比如说我们正写一个生成器,我们想要yield一个第二个生成器——一个子生成器(subgenerator)——生成的数。如果仅考虑产生(yield)的值,通过循环可以不费力的完成:

subgen = some_other_generator()
for v in subgen:
  yield v
登入後複製

然而,如果子生成器需要调用send()、throw()和close()和调用者适当交互的情况下,事情就复杂了。yield语句不得不通过类似于前一章节部分定义的try...except...finally结构来保证“调试”生成器函数。这种代码在PEP 380中提供,现在足够拿出将在Python 3.3中引入的新语法了:

yield from some_other_generator()
登入後複製

像上面的显式循环调用一样,重复从some_other_generator中产生值直到没有值可以产生,但是仍然向子生成器转发send、throw和close。

更多Python中的迭代器与生成器高级用法相关文章请关注PHP中文网!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

PHP和Python:解釋了不同的範例 PHP和Python:解釋了不同的範例 Apr 18, 2025 am 12:26 AM

PHP主要是過程式編程,但也支持面向對象編程(OOP);Python支持多種範式,包括OOP、函數式和過程式編程。 PHP適合web開發,Python適用於多種應用,如數據分析和機器學習。

在PHP和Python之間進行選擇:指南 在PHP和Python之間進行選擇:指南 Apr 18, 2025 am 12:24 AM

PHP適合網頁開發和快速原型開發,Python適用於數據科學和機器學習。 1.PHP用於動態網頁開發,語法簡單,適合快速開發。 2.Python語法簡潔,適用於多領域,庫生態系統強大。

Python vs. JavaScript:學習曲線和易用性 Python vs. JavaScript:學習曲線和易用性 Apr 16, 2025 am 12:12 AM

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

PHP和Python:深入了解他們的歷史 PHP和Python:深入了解他們的歷史 Apr 18, 2025 am 12:25 AM

PHP起源於1994年,由RasmusLerdorf開發,最初用於跟踪網站訪問者,逐漸演變為服務器端腳本語言,廣泛應用於網頁開發。 Python由GuidovanRossum於1980年代末開發,1991年首次發布,強調代碼可讀性和簡潔性,適用於科學計算、數據分析等領域。

vs code 可以在 Windows 8 中運行嗎 vs code 可以在 Windows 8 中運行嗎 Apr 15, 2025 pm 07:24 PM

VS Code可以在Windows 8上運行,但體驗可能不佳。首先確保系統已更新到最新補丁,然後下載與系統架構匹配的VS Code安裝包,按照提示安裝。安裝後,注意某些擴展程序可能與Windows 8不兼容,需要尋找替代擴展或在虛擬機中使用更新的Windows系統。安裝必要的擴展,檢查是否正常工作。儘管VS Code在Windows 8上可行,但建議升級到更新的Windows系統以獲得更好的開發體驗和安全保障。

visual studio code 可以用於 python 嗎 visual studio code 可以用於 python 嗎 Apr 15, 2025 pm 08:18 PM

VS Code 可用於編寫 Python,並提供許多功能,使其成為開發 Python 應用程序的理想工具。它允許用戶:安裝 Python 擴展,以獲得代碼補全、語法高亮和調試等功能。使用調試器逐步跟踪代碼,查找和修復錯誤。集成 Git,進行版本控制。使用代碼格式化工具,保持代碼一致性。使用 Linting 工具,提前發現潛在問題。

notepad 怎麼運行python notepad 怎麼運行python Apr 16, 2025 pm 07:33 PM

在 Notepad 中運行 Python 代碼需要安裝 Python 可執行文件和 NppExec 插件。安裝 Python 並為其添加 PATH 後,在 NppExec 插件中配置命令為“python”、參數為“{CURRENT_DIRECTORY}{FILE_NAME}”,即可在 Notepad 中通過快捷鍵“F6”運行 Python 代碼。

sublime怎麼運行代碼python sublime怎麼運行代碼python Apr 16, 2025 am 08:48 AM

在 Sublime Text 中運行 Python 代碼,需先安裝 Python 插件,再創建 .py 文件並編寫代碼,最後按 Ctrl B 運行代碼,輸出會在控制台中顯示。

See all articles