Python中的生成器與迭代器

Python中的生成器與迭代器

轉自:https://www.liaoxuefeng.com/wiki/1016959663602400/1017323698112640,推薦去該鏈接讀原文,有習題和熱烈的評論區交流。

生成器

通過列表生成式,我們可以直接創建一個列表。但是,受到內存限制,列表容量肯定是有限的。而且,創建一個包含100萬個元素的列表,不僅占用很大的存儲空間,如果我們僅僅需要訪問前面幾個元素,那后面絕大多數元素占用的空間都白白浪費了。

所以,如果列表元素可以按照某種算法推算出來,那我們是否可以在循環的過程中不斷推算出后續的元素呢?這樣就不必創建完整的list,從而節省大量的空間。在Python中,這種一邊循環一邊計算的機制,稱為生成器:generator。

要創建一個generator,有很多種方法。第一種方法很簡單,只要把一個列表生成式的[]改成(),就創建了一個generator:

>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>

創建Lg的區別僅在于最外層的[]()L是一個list,而g是一個generator。

我們可以直接打印出list的每一個元素,但我們怎么打印出generator的每一個元素呢?

如果要一個一個打印出來,可以通過next()函數獲得generator的下一個返回值:

>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> next(g)
16
>>> next(g)
25
>>> next(g)
36
>>> next(g)
49
>>> next(g)
64
>>> next(g)
81
>>> next(g)
Traceback (most recent call last):File "<stdin>", line 1, in <module>
StopIteration

我們講過,generator保存的是算法,每次調用next(g),就計算出g的下一個元素的值,直到計算到最后一個元素,沒有更多的元素時,拋出StopIteration的錯誤。

當然,上面這種不斷調用next(g)實在是太變態了,正確的方法是使用for循環,因為generator也是可迭代對象:

>>> g = (x * x for x in range(10))
>>> for n in g:
...     print(n)
... 
0
1
4
9
16
25
36
49
64
81

所以,我們創建了一個generator后,基本上永遠不會調用next(),而是通過for循環來迭代它,并且不需要關心StopIteration的錯誤。

generator非常強大。如果推算的算法比較復雜,用類似列表生成式的for循環無法實現的時候,還可以用函數來實現。

比如,著名的斐波拉契數列(Fibonacci),除第一個和第二個數外,任意一個數都可由前兩個數相加得到:

1, 1, 2, 3, 5, 8, 13, 21, 34, …

斐波拉契數列用列表生成式寫不出來,但是,用函數把它打印出來卻很容易:

def fib(max):n, a, b = 0, 0, 1while n < max:print(b)a, b = b, a + bn = n + 1return 'done'

注意,賦值語句:

a, b = b, a + b

相當于:

t = (b, a + b) # t是一個tuple
a = t[0]
b = t[1]

但不必顯式寫出臨時變量t就可以賦值。

上面的函數可以輸出斐波那契數列的前N個數:

>>> fib(6)
1
1
2
3
5
8
'done'

仔細觀察,可以看出,fib函數實際上是定義了斐波拉契數列的推算規則,可以從第一個元素開始,推算出后續任意的元素,這種邏輯其實非常類似generator。

也就是說,上面的函數和generator僅一步之遙。要把fib函數變成generator函數,只需要把print(b)改為yield b就可以了:

def fib(max):n, a, b = 0, 0, 1while n < max:yield ba, b = b, a + bn = n + 1return 'done'

這就是定義generator的另一種方法。如果一個函數定義中包含yield關鍵字,那么這個函數就不再是一個普通函數,而是一個generator函數,調用一個generator函數將返回一個generator:

>>> f = fib(6)
>>> f
<generator object fib at 0x104feaaa0>

這里,最難理解的就是generator函數和普通函數的執行流程不一樣。普通函數是順序執行,遇到return語句或者最后一行函數語句就返回。而變成generator的函數,在每次調用next()的時候執行,遇到yield語句返回,再次執行時從上次返回的yield語句處繼續執行。

舉個簡單的例子,定義一個generator函數,依次返回數字1,3,5:

def odd():print('step 1')yield 1print('step 2')yield(3)print('step 3')yield(5)

調用該generator函數時,首先要生成一個generator對象,然后用next()函數不斷獲得下一個返回值:

>>> o = odd()
>>> next(o)
step 1
1
>>> next(o)
step 2
3
>>> next(o)
step 3
5
>>> next(o)
Traceback (most recent call last):File "<stdin>", line 1, in <module>
StopIteration

可以看到,odd不是普通函數,而是generator函數,在執行過程中,遇到yield就中斷,下次又繼續執行。執行3次yield后,已經沒有yield可以執行了,所以,第4次調用next(o)就報錯。

請務必注意:調用generator函數會創建一個generator對象,多次調用generator函數會創建多個相互獨立的generator。

有的童鞋會發現這樣調用next()每次都返回1:

>>> next(odd())
step 1
1
>>> next(odd())
step 1
1
>>> next(odd())
step 1
1

原因在于odd()會創建一個新的generator對象,上述代碼實際上創建了3個完全獨立的generator,對3個generator分別調用next()當然每個都會返回第一個值。

正確的寫法是創建一個generator對象,然后不斷對這一個generator對象調用next()

>>> g = odd()
>>> next(g)
step 1
1
>>> next(g)
step 2
3
>>> next(g)
step 3
5

回到fib的例子,我們在循環過程中不斷調用yield,就會不斷中斷。當然要給循環設置一個條件來退出循環,不然就會產生一個無限數列出來。

同樣的,把函數改成generator函數后,我們基本上從來不會用next()來獲取下一個返回值,而是直接使用for循環來迭代:

>>> for n in fib(6):
...     print(n)
...
1
1
2
3
5
8

但是用for循環調用generator時,發現拿不到generator的return語句的返回值。如果想要拿到返回值,必須捕獲StopIteration錯誤,返回值包含在StopIterationvalue中:

>>> g = fib(6)
>>> while True:
...     try:
...         x = next(g)
...         print('g:', x)
...     except StopIteration as e:
...         print('Generator return value:', e.value)
...         break
...
g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
Generator return value: done

關于如何捕獲錯誤,后面的錯誤處理還會詳細講解。

小結

generator是非常強大的工具,在Python中,可以簡單地把列表生成式改成generator,也可以通過函數實現復雜邏輯的generator。

要理解generator的工作原理,它是在for循環的過程中不斷計算出下一個元素,并在適當的條件結束for循環。對于函數改成的generator來說,遇到return語句或者執行到函數體最后一行語句,就是結束generator的指令,for循環隨之結束。

請注意區分普通函數和generator函數,普通函數調用直接返回結果:

>>> r = abs(6)
>>> r
6

generator函數的調用實際返回一個generator對象:

>>> g = fib(6)
>>> g
<generator object fib at 0x1022ef948>

迭代器

我們已經知道,可以直接作用于for循環的數據類型有以下幾種:

一類是集合數據類型,如listtupledictsetstr等;

一類是generator,包括生成器和帶yield的generator function。

這些可以直接作用于for循環的對象統稱為可迭代對象:Iterable

可以使用isinstance()判斷一個對象是否是Iterable對象:

>>> from collections.abc import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False

而生成器不但可以作用于for循環,還可以被next()函數不斷調用并返回下一個值,直到最后拋出StopIteration錯誤表示無法繼續返回下一個值了。

可以被next()函數調用并不斷返回下一個值的對象稱為迭代器:Iterator

可以使用isinstance()判斷一個對象是否是Iterator對象:

>>> from collections.abc import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False

生成器都是Iterator對象,但listdictstr雖然是Iterable,卻不是Iterator

listdictstrIterable變成Iterator可以使用iter()函數:

>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True

你可能會問,為什么listdictstr等數據類型不是Iterator

這是因為Python的Iterator對象表示的是一個數據流,Iterator對象可以被next()函數調用并不斷返回下一個數據,直到沒有數據時拋出StopIteration錯誤。可以把這個數據流看做是一個有序序列,但我們卻不能提前知道序列的長度,只能不斷通過next()函數實現按需計算下一個數據,所以Iterator的計算是惰性的,只有在需要返回下一個數據時它才會計算。

Iterator甚至可以表示一個無限大的數據流,例如全體自然數。而使用list是永遠不可能存儲全體自然數的。

小結

凡是可作用于for循環的對象都是Iterable類型;

凡是可作用于next()函數的對象都是Iterator類型,它們表示一個惰性計算的序列;

集合數據類型如listdictstr等是Iterable但不是Iterator,不過可以通過iter()函數獲得一個Iterator對象。

Python的for循環本質上就是通過不斷調用next()函數實現的,例如:

for x in [1, 2, 3, 4, 5]:pass

實際上完全等價于:

# 首先獲得Iterator對象:
it = iter([1, 2, 3, 4, 5])
# 循環:
while True:try:# 獲得下一個值:x = next(it)except StopIteration:# 遇到StopIteration就退出循環break

迭代器與生成器的區別

來自原貼評論區大佬

生成器是迭代器的一種。所有實現了__next__()方法的對象都是迭代器。生成器本質上也是實現了該方法,不過是通過簡單的方法實現:函數中加yield關鍵字+使用類似列表生成式方式。這兩種方式創建出來的函數或者式子都是生成器,本質上也是迭代器。迭代器比較繁瑣的制造方式是創建一個對象,并在對象中實現一個方法:next()。實現之后,就可以反復調用next()方法返回值。#生成器當然也可以調用next()方法。所以生成器和迭代器都可以調用next()方法來獲取下一個返回值。這也是生成器和迭代器本質是一樣的原因。不同點在于,生成器書寫起來簡單明了,比通過創建一個對象并重寫__next__()方便多了。

生成器是一個用于創建迭代器的簡單而強大的工具,也就是說生成器也是迭代器。生成器較于一般的迭代器一是寫法更緊湊,因為它會自動創建 __iter__()__next__() 方法。另一點是每次在生成器上調用 next() 獲取yield返回的值時時,它會從上次離開的位置恢復執行(會記住上次執行語句時的所有數據值)。除了會自動創建方法和保存程序狀態,當生成器終結時,它們還會自動引發 StopIteration。 這些特性結合在一起,使得創建迭代器能與編寫常規函數一樣容易。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/532638.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/532638.shtml
英文地址,請注明出處:http://en.pswp.cn/news/532638.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于GET報錯的sql注入,sqli-lab 1~4

根據注入類型可將sql注入分為兩類&#xff1a;數字型和字符型 例如&#xff1a; 數字型&#xff1a; sleect * from table where if 用戶輸入id 字符型&#xff1a;select * from table where id 用戶輸入id &#xff08;有引號) 通過URL中修改對應的D值&#xff0c;為正常數字…

Python 裝飾器詳解(上)

Python 裝飾器詳解&#xff08;上&#xff09; 轉自&#xff1a;https://blog.csdn.net/qq_27825451/article/details/84396970&#xff0c;博主僅對其中 demo 實現中不適合python3 版本的語法進行修改&#xff0c;并微調了排版&#xff0c;本轉載博客全部例程博主均已親測可行…

xss原理和注入類型

XSS漏洞原理 : XSS又叫CSS(cross Site Script), 跨站腳本攻擊,指的是惡意攻擊者往Web頁面里插入惡意JS代碼,當用戶瀏覽該頁時,嵌入其中的Web里的JS代碼就會被執行,從而達到惡意的特殊目的. 比如:拿到cooike XSS漏洞分類: 反射性(非存儲型) payload沒有經過存儲,后端接收后,直接…

Python 裝飾器詳解(中)

Python 裝飾器詳解&#xff08;中&#xff09; 轉自&#xff1a;https://blog.csdn.net/qq_27825451/article/details/84581272&#xff0c;博主僅對其中 demo 實現中不適合python3 版本的語法進行修改&#xff0c;并微調了排版&#xff0c;本轉載博客全部例程博主均已親測可行…

存儲型xss案例

存儲型xss原理: 攻擊者在頁面插入xss代碼,服務端將數據存入數據庫,當用戶訪問存在xss漏洞的頁面時,服務端從數據庫取出數據展示到頁面上,導致xss代碼執行,達到攻擊效果 案例: 在一個搭建的論壇網站中, 根據存儲型xss注入的條件,要找到可以存儲到數據庫的輸入位置,并且這個位置…

反射型XSS案例

**原理:**攻擊者將url中插入xss代碼,服務端將url中的xss代碼輸出到頁面上,攻擊者將帶有xss代碼的url發送給用戶,用戶打開后受到xss攻擊 需要url中有可以修改的參數 案例: 可能存在反射型xss的功能(點) : 搜索框等&#xff08;所有url會出現參數的地方都可以嘗試&#xff09;……

Python 裝飾器詳解(下)

Python 裝飾器詳解&#xff08;下&#xff09; 轉自&#xff1a;https://blog.csdn.net/qq_27825451/article/details/84627016&#xff0c;博主僅對其中 demo 實現中不適合python3 版本的語法進行修改&#xff0c;并微調了排版&#xff0c;本轉載博客全部例程博主均已親測可行…

xss-lab靶場通關writeup(1~6.......在更新)

level 2 : 標簽被編碼&#xff0c;利用屬性完成彈窗 輸入 發現沒有彈窗 查看源代碼&#xff1a; 發現&#xff1a; <>符號被編碼 說明keybord參數進行了處理&#xff0c;那么只能從屬性上進行惡意編碼&#xff1a;先將屬性的引號和標簽閉合&#xff0c;用 // 將后面的…

PyTorch 分布式訓練DDP 單機多卡快速上手

PyTorch 分布式訓練DDP 單機多卡快速上手 本文旨在幫助新人快速上手最有效的 PyTorch 單機多卡訓練&#xff0c;對于 PyTorch 分布式訓練的理論介紹、多方案對比&#xff0c;本文不做詳細介紹&#xff0c;有興趣的讀者可參考&#xff1a; [分布式訓練] 單機多卡的正確打開方式…

Linux free 命令詳解

Linux free 命令詳解 free 命令用來查看系統中已用的和可用的內存。 命令選項及輸出簡介 關于各種命令的功能和命令選項&#xff0c;還是推薦英語比較好的同學直接看手冊 RTFM&#xff1a;man free。這里簡單總結一下一些重點&#xff1a; 功能及輸出簡介 free 命令顯示系…

CTF web題 wp:

1.簽到題 火狐F12查看源碼&#xff0c;發現注釋&#xff1a; 一次base64解碼出flag 2.Encode 在這里插入圖片描述 和第一題界面一樣&#xff1f;&#xff1f; 輕車熟路f12&#xff1a; 發現編碼&#xff1a; 格式看上去是base64&#xff0c;連續兩次base64后&#xff0c;觀…

【深度學習】深入理解Batch Normalization批歸一化

【深度學習】深入理解Batch Normalization批歸一化 轉自&#xff1a;https://www.cnblogs.com/guoyaohua/p/8724433.html 這幾天面試經常被問到BN層的原理&#xff0c;雖然回答上來了&#xff0c;但還是感覺答得不是很好&#xff0c;今天仔細研究了一下Batch Normalization的原…

ThinkPHP V5 漏洞利用

ThinkPHP 5漏洞簡介 ThinkPHP官方2018年12月9日發布重要的安全更新&#xff0c;修復了一個嚴重的遠程代碼執行漏洞。該更新主要涉及一個安全更新&#xff0c;由于框架對控制器名沒有進行足夠的檢測會導致在沒有開啟強制路由的情況下可能的getshell漏洞&#xff0c;受影響的版本…

Vim 重復操作的宏錄制

Vim 重復操作的宏錄制 轉自&#xff1a;https://www.cnblogs.com/ini_always/archive/2011/09/21/2184446.html 在編輯某個文件的時候&#xff0c;可能會出現需要對某種特定的操作進行許多次的情況&#xff0c;以編輯下面的文件為例&#xff1a; ; ;This is a sample config…

Vim 進階1

Vim 進階1 所有你覺得簡單重復&#xff0c;可以自動化實現的操作&#xff0c;都是可以自動化實現的。 Vim光標移動拾遺 w&#xff1a;下一個單詞的開頭&#xff0c;e&#xff1a;下一個單詞的結尾&#xff0c;b&#xff1a;上一個單詞的開頭&#xff0c; 0&#xff1a;行首…

攻防世界web題ics-06(爆破id值)

打開界面&#xff1a;嚯&#xff01;這花里胡哨 點來點去只有報表中心有回顯&#xff1a; 發現url中id等于1&#xff0c;sql注入嘗試無果&#xff0c; burp工具爆破id 對id的值進行爆破 burp報ERROR的話這是個bug&#xff0c;先點擊Hex后點decimal手動刷新就可以使用 強行總…

crontab用法與實例

crontab用法與實例 本文基于 ubuntu 18.04 在Linux系統的實際使用中&#xff0c;可能會經常碰到讓系統在某個特定時間執行某些任務的情況&#xff0c;比如定時采集服務器的狀態信息、負載狀況&#xff1b;定時執行某些任務/腳本來對遠端進行數據采集等。這里將介紹下crontab的配…

手工sql注入常規總結

1.發現注入點 2.報數據庫 先用單引號&#xff08;也嘗試雙引號&#xff09;閉合前面的語句&#xff0c;使注入的語句能夠執行&#xff0c; 數字 0 :匹配字段&#xff0c;還有 11 12 等等都可以使用&#xff0c;有些網站會有過濾處理&#xff0c;建議采用 1%2b12 1%2b1>1 繞…

Systemd入門教程:命令篇

Systemd入門教程&#xff1a;命令篇 轉自&#xff1a;http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html 作者&#xff1a; 阮一峰 日期&#xff1a; 2016年3月 7日 Systemd 是 Linux 系統工具&#xff0c;用來啟動守護進程&#xff0c;已成為大多數…

【CVE-2018-12613】phpmyadmin 4.8.1 遠程文件包含漏洞復現

**環境&#xff1a;**http://62.234.56.138:8080/server_databases.php 官網下載phpmyadmin 4.8.1 源碼&#xff1a;index.php文件中 函數含義&#xff1a; targer非空targer是否位字符串不能以index為開頭&#xff0c;即過濾了index值不能出現在blacklist內&#xff0c;即…