① python生成器簡介
Python 中的 yield 關鍵字鮮為人知,但是作用卻很大,族絕正是因為有了yield,才有了Python生成器。
yield 是 Python 的關鍵字,它用於 從函數返回而不破壞其局部變數的狀兆沒姿態 ,並且在調用該函數時,從最後一個 yield 語句開始執行。任何包含 yield 關鍵字的函數都稱為生成器。
Python 中的 yield 關鍵字的作用類似於 Python 中的 return 語句,不同之處在於:
yield的優點
yield的缺點
Python 可以使用 括弧() 創建生成器
更多時候,我們使用 yield 關鍵字創建生成器
下面這個生成器,前4次調用它時,返回的是0-3這幾個特殊值,第5次調用它時返回一個10-20之間的隨機整數。
更多時察旅候,生成器可以返回無限的值。
注意 generator() 函數返回的是一個生成器對象,要想獲取它的值,可以像上面那樣在迭代器中取出它的值,我們也可以顯式的調用next函數獲取值。
Python | yield Keyword - GeeksforGeeks:https://www.geeksforgeeks.org/python-yield-keyword/
https://c.runoob.com/compile/9/
② Python基礎之迭代器
一.什麼是迭代器
迭代器是用來迭代取值的工具。
而涉及到把多個值循環取出來的類型有:列表,字元串,元組,欄位,集合,打開文件等。通過使用的遍歷方式有for···in···,while等,但是,這些方式只適用於有索引的數據類型。為了解決索引取的局限性,python提供了一種 不依賴於索引的取值方式:迭代器
注意:
二.可迭代對象
可迭代對象:但凡內置有__iter__方法的都稱為可迭代對象
常見的可迭代對象:
1.集合數據類型,如list,tuple,dict,set,str等
2.生成器,包括生成器和帶yield的生成器函數。
三.如何創建迭代器
迭代器是一個包含數個值的對象。
迭代器是可以迭代的對象,這意味著您可以遍歷所有值。
從技術上講,在Python中,迭代器是實現迭代器協議的對象,該協議由方法 __iter__() 和 __next__() 組成。
簡而言之,一個類裡面實現了__iter__()和__next__()這兩個魔法方法,那麼這個類的對象就是可迭代對象。
四.迭代器的優缺點
1.優點
2.缺點
五.迭代器示例
另外,如果類Stu繼承了Iterator,那麼Stu可以不用實現__iter__()方法
遍歷迭代器
StopIteration
如果你有足夠的 next() 語句,或者在 for 循環中使用,則上面的例子將永遠進行下去。
為了防止迭代永遠進行,我們可以使用 StopIteration 語句。
在 __next__() 方法中,如果迭代完成指定的次數,我們可以添加一個終止條件來引發錯誤
③ Python中的「迭代」詳解
迭代器模式:一種惰性獲取數據項的方式,即按需一次獲取一個數據項。
所有序列都是可以迭代的。我們接下來要實現一個 Sentence(句子)類,我們向這個類的構造方法傳入包含一些文本的字元串,然後可以逐個單詞迭代。
接下來測試 Sentence 實例能否迭代
序列可以迭代的原因:
iter()
解釋器需要迭代對象 x 時,會自動調用iter(x)。
內置的 iter 函數有以下作用:
由於序列都實現了 __getitem__ 方法,所以都可以迭代。
可迭代對象:使用內置函數 iter() 可以獲取迭代器的對象。
與迭代器的關系:Python 從可迭代對象中獲取迭代器。
下面用for循環迭代一個字元串,這里字元串 'abc' 是可迭代的對象,用 for 循環迭代時是有生成器,只是 Python 隱藏了。
如果沒有 for 語句,使用 while 循環模擬,要寫成下面這樣:
Python 內部會處理 for 循環和其他迭代上下文(如列表推導,元組拆包等等)中的 StopIteration 異常。
標準的迭代器介面有兩個方法:
__next__ :返回下一個可用的元素,如果沒有元素了,拋出 StopIteration 異常。
__iter__ :返回 self,以便在需要使用可迭代對象的地方使用迭代器,如 for 循環中。
迭代器:實現了無參數的 __next__ 方法,返回序列中的下一個元素;如果沒有元素了,那麼拋出 StopIteration 異常。Python 中的迭代器還實現了 __iter__ 方法,因此迭代器也可以迭代。
接下來使用迭代器模式實現 Sentence 類:
注意, 不要 在 Sentence 類中實現 __next__ 方法,讓 Sentence 實例既是可迭代對象,也是自身的迭代器。
為了「支持多種遍歷」,必須能從同一個可迭代的實例中獲取多個獨立的迭代器,而且各個迭代器要能維護自身的內部狀態,因此這一模式正確的實現方式是,每次調用 iter(my_iterable) 都新建一個獨立的迭代器。
所以總結下來就是:
實現相同功能,但卻符合 Python 習慣的方式是,用生成器函數代替 SentenceIteror 類。
只要 Python 函數的定義體中有 yield 關鍵字,該函數就是生成器函數。調用生成器函數,就會返回一個生成器對象。
生成器函數會創建一個生成器對象,包裝生成器函數的定義體,把生成器傳給 next(...) 函數時,生成器函數會向前,執行函數定義體中的下一個 yield 語句,返回產出的值,並在函數定義體的當前位置暫停,。最終,函數的定義體返回時,外層的生成器對象會拋出 StopIteration 異常,這一點與迭代器協議一致。
如今這一版 Sentence 類相較之前簡短多了,但是還不夠慵懶。 惰性 ,是如今人們認為最好的特質。惰性實現是指盡可能延後生成值,這樣做能節省內存,或許還能避免做無用的處理。
目前實現的幾版 Sentence 類都不具有惰性,因為 __init__ 方法急迫的構建好了文本中的單詞列表,然後將其綁定到 self.words 屬性上。這樣就得處理整個文本,列表使用的內存量可能與文本本身一樣多(或許更多,取決於文本中有多少非單詞字元)。
re.finditer 函數是 re.findall 函數的惰性版本,返回的是一個生成器,按需生成 re.MatchObject 實例。我們可以使用這個函數來讓 Sentence 類變得懶惰,即只在需要時才生成下一個單詞。
標准庫提供了很多生成器函數,有用於逐行迭代純文本文件的對象,還有出色的 os.walk 函數等等。本節專注於通用的函數:參數為任意的可迭代對象,返回值是生成器,用於生成選中的、計算出的和重新排列的元素。
第一組是用於 過濾 的生成器函數:從輸入的可迭代對象中產出元素的子集,而且不修改元素本身。這種函數大多數都接受一個斷言參數(predicate),這個參數是個 布爾函數 ,有一個參數,會應用到輸入中的每個元素上,用於判斷元素是否包含在輸出中。
以下為這些函數的演示:
第二組是用於映射的生成器函數:在輸入的單個/多個可迭代對象中的各個元素上做計算,然後返回結果。
以下為這些函數的用法:
第三組是用於合並的生成器函數,這些函數都可以從輸入的多個可迭代對象中產出元素。
以下為演示:
第四組是從一個元素中產出多個值,擴展輸入的可迭代對象。
以下為演示:
第五組生成器函數用於產出輸入的可迭代對象中的全部元素,不過會以某種方式重新排列。
下面的函數都接受一個可迭代的對象,然後返回單個結果,這種函數叫「歸約函數」,「合攏函數」或「累加函數」,其實,這些內置函數都可以用 functools.rece 函數實現,但內置更加方便,而且還有一些優點。
參考教程:
《流暢的python》 P330 - 363
④ 自學python看什麼書比較好
《Learn Python the Hard Way》
本書是一本Python入門書籍,適合對計算機了解不多,沒有學過編程,但對編程感興趣的讀者學習使用。這本書以習題的方式引導讀者一步一步學習編程,從簡單的列印一直講到完整項目的實現,讓初學者從基礎的編程技術入手,最終體驗到軟體開發的基本過程。
本書結構非常簡單,共包括52個習題,其中26個覆蓋了輸入/輸出、變數和函數三個主題,另外26個覆蓋了一些比較高級的話題,如條件判斷、循環、類和對象、代碼測試及項目的實現等。每一章的格式基本相同,以代碼習題開始,按照說明編寫代碼,運行並檢查結果,然後再做附加練習。
《Python Cookbook》
同樣很有名。
本書介紹了Python應用在各個領域中的一些使用技巧和方法,從最基本的字元、文件序列、字典和排序,到進階的面向對象編程、資料庫和數據持久化、 XML處理和Web編程,再到比較高級和抽象的描述符、裝飾器、元類、迭代器和生成器,均有涉及。書中還介紹了一些第三方包和庫的使用,包括 Twisted、GIL、PyWin32等。本書覆蓋了Python應用中的很多常見問題,並提出了通用的解決方案。書中的代碼和方法具有很強的實用性,可以方便地應用到實際的項目中,並產生立竿見影的效果。
《A Byte Of Python》
中文《簡明Python教程》本書採用知識共享協議免費分發,意味著任何人都可以免費獲取,這本書走過了11個年頭,最新版以Python3為基礎同時也會兼顧到Python2的一些東西,內容非常精簡。
《Head first Python》
中文《深入淺出Python》這本書的內容通熟易懂,配有大量插圖,沒有長篇累牘地說教,讓你在學習過程中不會覺得枯燥,同為入門推薦書目。
《父與子的編程之旅》
這並不是關於親子關系的編程書,而是一本正兒八經Python編程入門書,只是以這種寓教於樂的形式闡述編程,顯得更輕松愉快一些。
《Effective Python》
本書可以幫你掌握真正的 Pythonic 編程方式,令你能夠完全發揮出Python語言的強大功能,並寫出健壯而高效的代碼
《流暢的Python》
本年度最好的一本Python進階書籍,從點到面、從最佳編程實踐深入到底層實現原理。每個章節配有大量參考鏈接,引導讀者進一步思考。
《Python源碼剖析》
深入Python底層原理,適合對Python實現原理感興趣的開發者閱讀。
《集體智慧編程》
一本注重實踐,以機器學習與計算統計為主題背景,講述如何挖掘和分析Web上的數據和資源的書,本書代碼示例以Python為主。入門人工智慧的都應該看看這本書。
《利用 Python 進行數據分析》
數據分析庫 pandas 作者寫的,數據分析入門就靠它了。
Python源碼剖析
內容比較深入,找到書看過,看的我一臉懵逼,所以這本書建議有一定基礎後嘗試去看,對於原理掌握大有裨益。
本書以CPython為研究對象,在C代碼一級,深入細致地剖析了Python的實現。書中不僅包括了對大量Python內置對象的剖析,更將大量的篇幅用於對Python虛擬機及Python高級特性的剖析。通過此書,讀者能夠透徹地理解Python中的一般表達式、控制結構、異常機制、類機制、多線程機制、模塊的動態載入機制、內存管理機制等核心技術的運行原理。
《Python核心編程(第二版)》
內容比較簡單,這版是2.x的入門資料。
·學習專業的Python風格、最佳實踐和好的編程習慣;
·加強對Python對象、內存模型和Python面向對象特性的深入理解;
·構建更有效的Web、CGI、互聯網、網路和其他客戶端/伺服器架構應用程序及軟體;
·學習如何使用Python中的Tkinter和其他工具來開發自己的GUI應用程序及軟體;
·通過用C等語言編寫擴展來提升Python應用程序的性能,或者通過使用多線程增強I/0相關的應用程序的能力;
·學習Python中有關資料庫的API,以及如何在Python中使用各種不同的資料庫系統,包括MySQL、Postgres和 SQLite。
《Python學習手冊》
無論你是剛接觸編程或者剛接觸Python,通過學習《Python學習手冊》,你可以迅速高效地精通核心Python語言基礎。讀完《Python學習手冊(第3版)》,你會對這門語言有足夠的了解,從而可以在你所從事的任何應用領域中使用它。《Python學習手冊(第3版)》讓你對Python語言有深入而完整的了解,從而幫助你理解今後遇到的任何Python應用程序實例。如果你准備探索Google和YouTube為什麼選中了Python,《Python學習手冊(第3版)》就是你入門的最佳指南。
《Python科學計算》
高階書,但是內容充實。
本書介紹如何用Python開發科學計算的應用程序,除了介紹數值計算之外,還著重介紹如何製作互動式的2D、3D圖像,如何設計精巧的程序界面,如何與C語言編寫的高速計算程序結合,如何編寫聲音、圖像處理演算法等內容。書中涉及的Python擴展庫包括NumPy、SciPy、SymPy、matplotlib、Traits、TraitsUI、Chaco、TVTK、Mayavi、VPython、OpenCV等,涉及的應用領域包括數值運算、符號運算、二維圖表、三維數據可視化、三維動畫演示、圖像處理以及界面設計等。
《Python標准庫》
對於程序員而言,標准庫與語言本身同樣重要,它好比一個百寶箱,能為各種常見的任務提供完美的解決方案,所以本書是所有Python程序員都必備的工具書!本書以案例驅動的方式講解了標准庫中一百多個模塊的使用方法(如何工作)和工作原理(為什麼要這樣工作),比標准庫的官方文檔更容易理解(一個簡單的示例比一份手冊文檔更有幫助),為Python程序員熟練掌握和使用這些模塊提供了絕佳指導。
《像計算機科學家一樣思考Python》
當初就是這本書帶我入了坑。
《像計算機科學家一樣思考python》按照培養讀者像計算機科學家一樣的思維方式的思路來教授python語言編程。全書貫穿的主體是如何思考、設計、開發的方法,而具體的編程語言,只是提供一個具體場景方便介紹的媒介。《像計算機科學家一樣思考python》並不是一本介紹語言的書,而是一本介紹編程思想的書。和其他編程設計語言書籍不同,它不拘泥於語言細節,而是嘗試從初學者的角度出發,用生動的示例和豐富的練習來引導讀者漸入佳境。
作為一個新人,這幾本書已經夠多了——如果你仔細讀,這些書足夠幫你完成中級Python程序員進階。
⑤ Python中的for循環、可迭代對象、迭代器和生成器-
問題:
「迭代是重復反饋過程的活動,其目的通常是為了逼近所需目標或結果。」在Python中,可迭代對象、迭代器、for循環都是和「迭代」密切相關的知識點。
下面我們試著通過實現自定義一下list的迭代過程:
迭代器和生成器總是會被同時提起,那麼它們之間有什麼關聯呢——生成器是一種特殊的迭代器。
⑥ Python|range函數用法完全解讀
迭代器是 23 種設計模式中最常用的一種(之一),在 Python 中隨處可見它的身影,我們經常用到它,但是卻不一定意識到它的存在。在關於迭代器的系列文章中(鏈接見文末),我至少提到了 23 種生成迭代器的方法。有些方法是專門用於生成迭代器的,還有一些方法則是為了解決別的問題而「暗中」使用到迭代器。
在系統學習迭代器之前,我一直以為 range() 方法也是用於生成迭代器的,現在卻突然發現,它生成的只是可迭代對象,而並不是迭代器! (PS:Python2 中 range() 生成的是列表,本文基於Python3,生成的是可迭代對象)
於是,我有了這樣的疑問:為什麼 range() 不生成迭代器呢?在查找答案的過程中,我發現自己對 range 類型的認識存在一些誤區。因此,本文將和大家全面地認識一下 range ,期待與你共同學習進步。
1、range() 是什麼?
它的語法:range(start, stop [,step]) ;start 指的是計數起始值,默認是 0;stop 指的是計數結束值,但不包括 stop ;step 是步長,默認為 1,不可以為 0 。range() 方法生成一段左閉右開的整數范圍。
對於 range() 函數,有幾個注意點:(1)它表示的是左閉右開區間;(2)它接收的參數必須是整數,可以是負數,但不能是浮點數等其它類型;(3)它是不可變的序列類型,可以進行判斷元素、查找元素、切片等操作,但不能修改元素;(4)它是可迭代對象,卻不是迭代器。
2、 為什麼range()不生產迭代器?
可以獲得迭代器的內置方法很多,例如 zip() 、enumerate()、map()、filter() 和 reversed() 等等,但是像 range() 這樣僅僅得到的是可迭代對象的方法就絕無僅有了(若有反例,歡迎告知)。這就是我存在知識誤區的地方。
在 for-循環 遍歷時,可迭代對象與迭代器的性能是一樣的,即它們都是惰性求值的,在空間復雜度與時間復雜度上並無差異。我曾概括過兩者的差別是「一同兩不同」:相同的是都可惰性迭代,不同的是可迭代對象不支持自遍歷(即next()方法),而迭代器本身不支持切片(即 getitem () 方法)。
雖然有這些差別,但很難得出結論說它們哪個更優。現在微妙之處就在於,為什麼給 5 種內置方法都設計了迭代器,偏偏給 range() 方法設計的就是可迭代對象呢?把它們都統一起來,不是更好么?
事實上,Pyhton 為了規范性就干過不少這種事,例如,Python2 中有 range() 和 xrange() 兩種方法,而 Python3 就幹掉了其中一種,還用了「李代桃僵」法。為什麼不更規范點,令 range() 生成的是迭代器呢?
關於這個問題,我沒找到官方解釋,以下純屬個人觀點 。
zip() 等方法都需要接收確定的可迭代對象的參數,是對它們的一種再加工的過程,因此也希望馬上產出確定的結果來,所以 Python 開發者就設計了這個結果是迭代器。這樣還有一個好處,即當作為參數的可迭代對象發生變化的時候,作為結果的迭代器因為是消耗型的,不會被錯誤地使用。
而 range() 方法就不同了,它接收的參數不是可迭代對象,本身是一種初次加工的過程,所以設計它為可迭代對象,既可以直接使用,也可以用於其它再加工用途。例如,zip() 等方法就完全可以接收 range 類型的參數。
也就是說,range() 方法作為一種初級生產者,它生產的原料本身就有很大用途,早早把它變為迭代器的話,無疑是一種畫蛇添足的行為。
對於這種解讀,你是否覺得有道理呢?歡迎就這個話題與我探討。
3、range 類型是什麼?
以上是我對「為什麼range()不產生迭代器」的一種解答。順著這個思路,我研究了一下它產生的 range 對象,一研究就發現,這個 range 對象也並不簡單。
首先奇怪的一點就是,它竟然是不可變序列!我從未注意過這一點。雖然說,我從未想過修改 range() 的值,但這一不可修改的特性還是令我驚訝。
翻看文檔,官方是這樣明確劃分的——有三種基本的序列類型:列表、元組和范圍(range)對象。(There are three basic sequence types: lists, tuples, and range objects.)
這我倒一直沒注意,原來 range 類型居然跟列表和元組是一樣地位的基礎序列!我一直記掛著字元串是不可變的序列類型,不曾想,這里還有一位不可變的序列類型呢。
那 range 序列跟其它序列類型有什麼差異呢?
普通序列都支持的操作有 12 種。range 序列只支持其中的 10 種,不支持進行加法拼接與乘法重復。
那麼問題來了:同樣是不可變序列,為什麼字元串和元組就支持上述兩種操作,而偏偏 range 序列不支持呢?雖然不能直接修改不可變序列,但我們可以將它們拷貝到新的序列上進行操作啊,為何 range 對象連這都不支持呢?
且看官方文檔的解釋:
…e to the fact that range objects can only represent sequences that follow a strict pattern and repetition and concatenation will usually violate that pattern.
原因是 range 對象僅僅表示一個遵循著嚴格模式的序列,而重復與拼接通常會破壞這種模式…
問題的關鍵就在於 range 序列的 pattern,仔細想想,其實它表示的就是一個等差數列啊(喵,高中數學知識沒忘…),拼接兩個等差數列,或者重復拼接一個等差數列,想想確實不妥,這就是為啥 range 類型不支持這兩個操作的原因了。由此推論,其它修改動作也會破壞等差數列結構,所以統統不給修改就是了。
4、小結
回顧全文,我得到了兩個偏冷門的結論:range 是可迭代對象而不是迭代器;range 對象是不可變的等差序列。
若單純看結論的話,你也許沒有感觸,或許還會說這沒啥了不得啊。但如果我追問,為什麼 range 不是迭代器呢,為什麼 range 是不可變序列呢?對這倆問題,你是否還能答出個自圓其說的設計思想呢?(PS:我決定了,若有機會面試別人,我必要問這兩個問題的嘿~)
由於 range 對象這細微而有意思的特性,我覺得這篇文章寫得值了。本文是作為迭代器系列文章的一篇來寫的,所以對於迭代器的基礎知識介紹不多,另外,還有一種特殊的迭代器也值得單獨成文,那就是生成器了。
⑦ 如何更好地理解Python迭代器和生成器
在Python這門語言中,生成器毫無疑問是最有用的特性之一。與此同時,也是使用的最不廣泛的Python特性之一。究其原因,主要是因為,在其他主流語言裡面沒有生成器的概念。正是由於生成器是一個「新」的東西,所以,它一方面沒有引起廣大工程師的重視,另一方面,也增加了工程師的學習成本,最終導致大家錯過了Python中如此有用的一個特性。
我的這篇文章,希望通過簡單易懂的方式,深入淺出地介紹Python的生成器,以改變「如此有用的特性卻使用極不廣泛」的現象。本文的組織如下:在第1章,我們簡單地介紹了Python中的迭代器協議;在本文第2章,將會詳細介紹生成器的概念和語法;在第3章,將會給出一個有用的例子,說明使用生成器的好處;在本文最後,簡單的討論了使用生成器的注意事項。
1. 迭代器協議
由於生成器自動實現了迭代器協議,而迭代器協議對很多人來說,也是一個較為抽象的概念。所以,為了更好的理解生成器,我們需要簡單的回顧一下迭代器協議的概念。
迭代器協議是指:對象需要提供next方法,它要麼返回迭代中的下一項,要麼就引起一個StopIteration異常,以終止迭代
可迭代對象就是:實現了迭代器協議的對象
協議是一種約定,可迭代對象實現迭代器協議,Python的內置工具(如for循環,sum,min,max函數等)使用迭代器協議訪問對象。
舉個例子:在所有語言中,我們都可以使用for循環來遍歷數組,Python的list底層實現是一個數組,所以,我們可以使用for循環來遍歷list。如下所示:
>>> for n in [1, 2, 3, 4]:
... print n
但是,對Python稍微熟悉一點的朋友應該知道,Python的for循環不但可以用來遍歷list,還可以用來遍歷文件對象,如下所示:
>>> with open(『/etc/passwd』) as f: # 文件對象提供迭代器協議
... for line in f: # for循環使用迭代器協議訪問文件
... print line
...
為什麼在Python中,文件還可以使用for循環進行遍歷呢?這是因為,在Python中,文件對象實現了迭代器協議,for循環並不知道它遍歷的是一個文件對象,它只管使用迭代器協議訪問對象即可。正是由於Python的文件對象實現了迭代器協議,我們才得以使用如此方便的方式訪問文件,如下所示:
>>> f = open('/etc/passwd')
>>> dir(f)
['__class__', '__enter__', '__exit__', '__iter__', '__new__', 'writelines', '...'
2. 生成器
Python使用生成器對延遲操作提供了支持。所謂延遲操作,是指在需要的時候才產生結果,而不是立即產生結果。這也是生成器的主要好處。
Python有兩種不同的方式提供生成器:
生成器函數:常規函數定義,但是,使用yield語句而不是return語句返回結果。yield語句一次返回一個結果,在每個結果中間,掛起函數的狀態,以便下次重它離開的地方繼續執行
生成器表達式:類似於列表推導,但是,生成器返回按需產生結果的一個對象,而不是一次構建一個結果列表
2.1 生成器函數
我們來看一個例子,使用生成器返回自然數的平方(注意返回的是多個值):
def gensquares(N):
for i in range(N):
yield i ** 2
for item in gensquares(5):
print item,
使用普通函數:
def gensquares(N):
res = []
for i in range(N):
res.append(i*i)
return res
for item in gensquares(5):
print item,
可以看到,使用生成器函數代碼量更少。
2.2 生成器表達式
使用列表推導,將會一次產生所有結果:
>>> squares = [x**2 for x in range(5)]
>>> squares
[0, 1, 4, 9, 16]
將列表推導的中括弧,替換成圓括弧,就是一個生成器表達式:
>>> squares = (x**2 for x in range(5))
>>> squares
<generator object at 0x00B2EC88>
>>> next(squares)
0
>>> next(squares)
1
>>> next(squares)
4
>>> list(squares)
[9, 16]
Python不但使用迭代器協議,讓for循環變得更加通用。大部分內置函數,也是使用迭代器協議訪問對象的。例如, sum函數是Python的內置函數,該函數使用迭代器協議訪問對象,而生成器實現了迭代器協議,所以,我們可以直接這樣計算一系列值的和:
>>> sum(x ** 2 for x in xrange(4))
而不用多此一舉的先構造一個列表:
>>> sum([x ** 2 for x in xrange(4)])
2.3 再看生成器
前面已經對生成器有了感性的認識,我們以生成器函數為例,再來深入探討一下Python的生成器:
語法上和函數類似:生成器函數和常規函數幾乎是一樣的。它們都是使用def語句進行定義,差別在於,生成器使用yield語句返回一個值,而常規函數使用return語句返回一個值
自動實現迭代器協議:對於生成器,Python會自動實現迭代器協議,以便應用到迭代背景中(如for循環,sum函數)。由於生成器自動實現了迭代器協議,所以,我們可以調用它的next方法,並且,在沒有值可以返回的時候,生成器自動產生StopIteration異常
狀態掛起:生成器使用yield語句返回一個值。yield語句掛起該生成器函數的狀態,保留足夠的信息,以便之後從它離開的地方繼續執行
3. 示例
我們再來看兩個生成器的例子,以便大家更好的理解生成器的作用。
首先,生成器的好處是延遲計算,一次返回一個結果。也就是說,它不會一次生成所有的結果,這對於大數據量處理,將會非常有用。
大家可以在自己電腦上試試下面兩個表達式,並且觀察內存佔用情況。對於前一個表達式,我在自己的電腦上進行測試,還沒有看到最終結果電腦就已經卡死,對於後一個表達式,幾乎沒有什麼內存佔用。
sum([i for i in xrange(10000000000)])
sum(i for i in xrange(10000000000))
除了延遲計算,生成器還能有效提高代碼可讀性。例如,現在有一個需求,求一段文字中,每個單詞出現的位置。
不使用生成器的情況:
def index_words(text):
result = []
if text:
result.append(0)
for index, letter in enumerate(text, 1):
if letter == ' ':
result.append(index)
return result
使用生成器的情況:
def index_words(text):
if text:
yield 0
for index, letter in enumerate(text, 1):
if letter == ' ':
yield index
這里,至少有兩個充分的理由說明 ,使用生成器比不使用生成器代碼更加清晰:
使用生成器以後,代碼行數更少。大家要記住,如果想把代碼寫的Pythonic,在保證代碼可讀性的前提下,代碼行數越少越好
不使用生成器的時候,對於每次結果,我們首先看到的是result.append(index),其次,才是index。也就是說,我們每次看到的是一個列表的append操作,只是append的是我們想要的結果。使用生成器的時候,直接yield index,少了列表append操作的干擾,我們一眼就能夠看出,代碼是要返回index。
這個例子充分說明了,合理使用生成器,能夠有效提高代碼可讀性。只要大家完全接受了生成器的概念,理解了yield語句和return語句一樣,也是返回一個值。那麼,就能夠理解為什麼使用生成器比不使用生成器要好,能夠理解使用生成器真的可以讓代碼變得清晰易懂。
4. 使用生成器的注意事項
相信通過這篇文章,大家已經能夠理解生成器的作用和好處。但是,還沒有結束,使用生成器,也有一點注意事項。
我們直接來看例子,假設文件中保存了每個省份的人口總數,現在,需要求每個省份的人口佔全國總人口的比例。顯然,我們需要先求出全國的總人口,然後在遍歷每個省份的人口,用每個省的人口數除以總人口數,就得到了每個省份的人口佔全國人口的比例。
如下所示:
def get_province_population(filename):
with open(filename) as f:
for line in f:
yield int(line)
gen = get_province_population('data.txt')
all_population = sum(gen)
#print all_population
for population in gen:
print population / all_population
執行上面這段代碼,將不會有任何輸出,這是因為,生成器只能遍歷一次。在我們執行sum語句的時候,就遍歷了我們的生成器,當我們再次遍歷我們的生成器的時候,將不會有任何記錄。所以,上面的代碼不會有任何輸出。
因此,生成器的唯一注意事項就是:生成器只能遍歷一次。
5. 總結
本文深入淺出地介紹了Python中,一個容易被大家忽略的重要特性,即Python的生成器。為了講解生成器,本文先介紹了迭代器協議,然後介紹了生成器函數和生成器表達式,並通過示例演示了生成器的優點和注意事項。在實際工作中,充分利用Python生成器,不但能夠減少內存使用,還能夠提高代碼可讀性。掌握生成器也是Python高手的標配。希望本文能夠幫助大家理解Python的生成器。