![函數(shù)式編程介紹-Python版_第1頁](http://file4.renrendoc.com/view/70d8dd42c5c657eba0221796eabfc47a/70d8dd42c5c657eba0221796eabfc47a1.gif)
![函數(shù)式編程介紹-Python版_第2頁](http://file4.renrendoc.com/view/70d8dd42c5c657eba0221796eabfc47a/70d8dd42c5c657eba0221796eabfc47a2.gif)
![函數(shù)式編程介紹-Python版_第3頁](http://file4.renrendoc.com/view/70d8dd42c5c657eba0221796eabfc47a/70d8dd42c5c657eba0221796eabfc47a3.gif)
![函數(shù)式編程介紹-Python版_第4頁](http://file4.renrendoc.com/view/70d8dd42c5c657eba0221796eabfc47a/70d8dd42c5c657eba0221796eabfc47a4.gif)
![函數(shù)式編程介紹-Python版_第5頁](http://file4.renrendoc.com/view/70d8dd42c5c657eba0221796eabfc47a/70d8dd42c5c657eba0221796eabfc47a5.gif)
版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
1.函數(shù)式編程概述1.1.什么是函數(shù)式編程?函數(shù)式編程使用一系列的函數(shù)解決問題。函數(shù)僅接受輸入并產(chǎn)生輸出,不包含任何能影響產(chǎn)生輸出的內(nèi)部狀態(tài)。任何情況下,使用相同的參數(shù)調(diào)用函數(shù)始終能產(chǎn)生同樣的結(jié)果。在一個函數(shù)式的程序中,輸入的數(shù)據(jù)“流過”一系列的函數(shù),每一個函數(shù)根據(jù)它的輸入產(chǎn)生輸出。函數(shù)式風(fēng)格避免編寫有“邊界效應(yīng)”(sideeffects)的函數(shù):修改內(nèi)部狀態(tài),或者是其他無法反應(yīng)在輸出上的變化。完全沒有邊界效應(yīng)的函數(shù)被稱為“純函數(shù)式的”(purelyfunctional)。避免邊界效應(yīng)意味著不使用在程序運行時可變的數(shù)據(jù)結(jié)構(gòu),輸出只依賴于輸入??梢哉J為函數(shù)式編程剛好站在了面向?qū)ο缶幊痰膶α⒚?。對象通常包含?nèi)部狀態(tài)(字段),和許多能修改這些狀態(tài)的函數(shù),程序則由不斷修改狀態(tài)構(gòu)成;函數(shù)式編程則極力避免狀態(tài)改動,并通過在函數(shù)間傳遞數(shù)據(jù)流進行工作。但這并不是說無法同時使用函數(shù)式編程和面向?qū)ο缶幊?,事實上,?fù)雜的系統(tǒng)一般會采用面向?qū)ο蠹夹g(shù)建模,但混合使用函數(shù)式風(fēng)格還能讓你額外享受函數(shù)式風(fēng)格的優(yōu)點。1.2.為什么使用函數(shù)式編程?函數(shù)式的風(fēng)格通常被認為有如下優(yōu)點:邏輯可證——這是一個學(xué)術(shù)上的優(yōu)點:沒有邊界效應(yīng)使得更容易從邏輯上證明程序是正確的(而不是通過測試)。模塊化——函數(shù)式編程推崇簡單原則,一個函數(shù)只做一件事情,將大的功能拆分成盡可能小的模塊。小的函數(shù)更易于閱讀和檢查錯誤。組件化——小的函數(shù)更容易加以組合形成新的功能。易于調(diào)試——細化的、定義清晰的函數(shù)使得調(diào)試更加簡單。當(dāng)程序不正常運行時,每一個函數(shù)都是檢查數(shù)據(jù)是否正確的接口,能更快速地排除沒有問題的代碼,定位到出現(xiàn)問題的地方。易于測試——不依賴于系統(tǒng)狀態(tài)的函數(shù)無須在測試前構(gòu)造測試樁,使得編寫單元測試更加容易。更高的生產(chǎn)率——函數(shù)式編程產(chǎn)生的代碼比其他技術(shù)更少(往往是其他技術(shù)的一半左右),并且更容易閱讀和維護。1.3.如何辨認函數(shù)式風(fēng)格?支持函數(shù)式編程的語言通常具有如下特征,大量使用這些特征的代碼即可被認為是函數(shù)式的:函數(shù)是一等公民
函數(shù)能作為參數(shù)傳遞,或者是作為返回值返回。這個特性使得模板方法模式非常易于編寫,這也促使了這個模式被更頻繁地使用。
以一個簡單的集合排序為例,假設(shè)lst是一個數(shù)集,并擁有一個排序方法sort需要將如何確定順序作為參數(shù)。
如果函數(shù)不能作為參數(shù),那么lst的sort方法只能接受普通對象作為參數(shù)。這樣一來我們需要首先定義一個接口,然后定義一個實現(xiàn)該接口的類,最后將該類的一個實例傳給sort方法,由sort調(diào)用這個實例的compare方法,就像這樣:#偽代碼interfaceComparator{
compare(o1,o2)}lst=list(range(5))lst.sort(Comparator(){
compare(o1,o2){
returno2-o1//逆序})可見,我們定義了一個新的接口、新的類型(這里是一個匿名類),并new了一個新的對象只為了調(diào)用一個方法。如果這個方法可以直接作為參數(shù)傳遞會怎樣呢?看起來應(yīng)該像這樣:defcompare(o1,o2):
returno2-o1#逆序lst=list(range(5))lst.sort(compare)請注意,前一段代碼已經(jīng)使用了匿名類技巧從而省下了不少代碼,但仍然不如直接傳遞函數(shù)簡單、自然。匿名函數(shù)(lambda)
lambda提供了快速編寫簡單函數(shù)的能力。對于偶爾為之的行為,lambda讓你不再需要在編碼時跳轉(zhuǎn)到其他位置去編寫函數(shù)。
lambda表達式定義一個匿名的函數(shù),如果這個函數(shù)僅在編碼的位置使用到,你可以現(xiàn)場定義、直接使用:lst.sort(lambdao1,o2:pareTo(o2))相信從這個小小的例子你也能感受到強大的生產(chǎn)效率:)封裝控制結(jié)構(gòu)的內(nèi)置模板函數(shù)
為了避開邊界效應(yīng),函數(shù)式風(fēng)格盡量避免使用變量,而僅僅為了控制流程而定義的循環(huán)變量和流程中產(chǎn)生的臨時變量無疑是最需要避免的。
假如我們需要對剛才的數(shù)集進行過濾得到所有的正數(shù),使用指令式風(fēng)格的代碼應(yīng)該像是這樣:lst2=list()foriinrange(len(lst)):#模擬經(jīng)典for循環(huán)
iflst[i]>0:
lst2.append(lst[i])這段代碼把從創(chuàng)建新列表、循環(huán)、取出元素、判斷、添加至新列表的整個流程完整的展示了出來,儼然把解釋器當(dāng)成了需要手把手指導(dǎo)的傻瓜。然而,“過濾”這個動作是很常見的,為什么解釋器不能掌握過濾的流程,而我們只需要告訴它過濾規(guī)則呢?
在Python里,過濾由一個名為filter的內(nèi)置函數(shù)實現(xiàn)。有了這個函數(shù),解釋器就學(xué)會了如何“過濾”,而我們只需要把規(guī)則告訴它:lst2=filter(lambdan:n>0,lst)這個函數(shù)帶來的好處不僅僅是少寫了幾行代碼這么簡單。
封裝控制結(jié)構(gòu)后,代碼中就只需要描述功能而不是做法,這樣的代碼更清晰,更可讀。因為避開了控制結(jié)構(gòu)的干擾,第二段代碼顯然能讓你更容易了解它的意圖。
另外,因為避開了索引,使得代碼中不太可能觸發(fā)下標(biāo)越界這種異常,除非你手動制造一個。
函數(shù)式編程語言通常封裝了數(shù)個類似“過濾”這樣的常見動作作為模板函數(shù)。唯一的缺點是這些函數(shù)需要少量的學(xué)習(xí)成本,但這絕對不能掩蓋使用它們帶來的好處。閉包(closure)
閉包是綁定了外部作用域的變量(但不是全局變量)的函數(shù)。大部分情況下外部作用域指的是外部函數(shù)。
閉包包含了自身函數(shù)體和所需外部函數(shù)中的“變量名的引用”。引用變量名意味著綁定的是變量名,而不是變量實際指向的對象;如果給變量重新賦值,閉包中能訪問到的將是新的值。
閉包使函數(shù)更加靈活和強大。即使程序運行至離開外部函數(shù),如果閉包仍然可見,則被綁定的變量仍然有效;每次運行至外部函數(shù),都會重新創(chuàng)建閉包,綁定的變量是不同的,不需要擔(dān)心在舊的閉包中綁定的變量會被新的值覆蓋。
回到剛才過濾數(shù)集的例子。假設(shè)過濾條件中的0這個邊界值不再是固定的,而是由用戶控制。如果沒有閉包,那么代碼必須修改為:classgreater_than_helper:
def__init__(self,minval):
self.minval=minval
defis_greater_than(self,val):
returnval>self.minval
defmy_filter(lst,minval):
helper=greater_than_helper(minval)
returnfilter(helper.is_greater_than,lst)請注意我們現(xiàn)在已經(jīng)為過濾功能編寫了一個函數(shù)my_filter。如你所見,我們需要在別的地方(此例中是類greater_than_helper)持有另一個操作數(shù)minval。
如果支持閉包,因為閉包可以直接使用外部作用域的變量,我們就不再需要greater_than_helper了:defmy_filter(lst,minval):
returnfilter(lambdan:n>minval,lst)可見,閉包在不影響可讀性的同時也省下了不少代碼量。
函數(shù)式編程語言都提供了對閉包的不同程度的支持。在Python2.x中,閉包無法修改綁定變量的值,所有修改綁定變量的行為都被看成新建了一個同名的局部變量并將綁定變量隱藏。Python3.x中新加入了一個關(guān)鍵字nonlocal以支持修改綁定變量。但不管支持程度如何,你始終可以訪問(讀?。┙壎ㄗ兞?。內(nèi)置的不可變數(shù)據(jù)結(jié)構(gòu)
為了避開邊界效應(yīng),不可變的數(shù)據(jù)結(jié)構(gòu)是函數(shù)式編程中不可或缺的部分。不可變的數(shù)據(jù)結(jié)構(gòu)保證數(shù)據(jù)的一致性,極大地降低了排查問題的難度。
例如,Python中的元組(tuple)就是不可變的,所有對元組的操作都不能改變元組的內(nèi)容,所有試圖修改元組內(nèi)容的操作都會產(chǎn)生一個異常。
函數(shù)式編程語言一般會提供數(shù)據(jù)結(jié)構(gòu)的兩種版本(可變和不可變),并推薦使用不可變的版本。遞歸
遞歸是另一種取代循環(huán)的方法。遞歸其實是函數(shù)式編程很常見的形式,經(jīng)??梢栽谝恍┧惴ㄖ幸姷健5苑诺阶詈?,是因為實際上我們一般很少用到遞歸。如果一個遞歸無法被編譯器或解釋器優(yōu)化,很容易就會產(chǎn)生棧溢出;另一方面復(fù)雜的遞歸往往讓人感覺迷惑,不如循環(huán)清晰,所以眾多最佳實踐均指出使用循環(huán)而非遞歸。
這一系列短文中都不會關(guān)注遞歸的使用。2.從函數(shù)開始2.1.定義一個函數(shù)如下定義了一個求和函數(shù):defadd(x,y):
returnx+y關(guān)于參數(shù)和返回值的語法細節(jié)可以參考其他文檔,這里就略過了。使用lambda可以定義簡單的單行匿名函數(shù)。lambda的語法是:lambdaargs:expression參數(shù)(args)的語法與普通函數(shù)一樣,同時表達式(expression)的值就是匿名函數(shù)調(diào)用的返回值;而lambda表達式返回這個匿名函數(shù)。如果我們給匿名函數(shù)取個名字,就像這樣:lambda_add=lambdax,y:x+y這與使用def定義的求和函數(shù)完全一樣,可以使用lambda_add作為函數(shù)名進行調(diào)用。然而,提供lambda的目的是為了編寫偶爾為之的、簡單的、可預(yù)見不會被修改的匿名函數(shù)。這種風(fēng)格雖然看起來很酷,但并不是一個好主意,特別是當(dāng)某一天需要對它進行擴充,再也無法用一個表達式寫完時。如果一開始就需要給函數(shù)命名,應(yīng)該始終使用def關(guān)鍵字。2.2.使用函數(shù)賦值事實上你已經(jīng)見過了,上一節(jié)中我們將lambda表達式賦值給了add。同樣,使用def定義的函數(shù)也可以賦值,相當(dāng)于為函數(shù)取了一個別名,并且可以使用這個別名調(diào)用函數(shù):add_a_number_to_another_one_by_using_plus_operator=addprintadd_a_number_to_another_one_by_using_plus_operator(1,2)既然函數(shù)可以被變量引用,那么將函數(shù)作為參數(shù)和返回值就是很尋常的做法了。2.3.閉包閉包是一類特殊的函數(shù)。如果一個函數(shù)定義在另一個函數(shù)的作用域中,并且函數(shù)中引用了外部函數(shù)的局部變量,那么這個函數(shù)就是一個閉包。下面的代碼定義了一個閉包:deff():
n=1
definner():
printn
inner()
n='x'
inner()函數(shù)inner定義在f的作用域中,并且在inner中使用了f中的局部變量n,這就構(gòu)成了一個閉包。閉包綁定了外部的變量,所以調(diào)用函數(shù)f的結(jié)果是打印1和'x'。這類似于普通的模塊函數(shù)和模塊中定義的全局變量的關(guān)系:修改外部變量能影響內(nèi)部作用域中的值,而在內(nèi)部作用域中定義同名變量則將遮蔽(隱藏)外部變量。如果需要在函數(shù)中修改全局變量,可以使用關(guān)鍵字global修飾變量名。Python2.x中沒有關(guān)鍵字為在閉包中修改外部變量提供支持,在3.x中,關(guān)鍵字nonlocal可以做到這一點:#Python3.xsupports`nonlocal'deff():
n=1
definner():
nonlocaln
n='x'
print(n)
inner()
print(n)調(diào)用這個函數(shù)的結(jié)果是打印1和'x',如果你有一個Python3.x的解釋器,可以試著運行一下。由于使用了函數(shù)體外定義的變量,看起來閉包似乎違反了函數(shù)式風(fēng)格的規(guī)則即不依賴外部狀態(tài)。但是由于閉包綁定的是外部函數(shù)的局部變量,而一旦離開外部函數(shù)作用域,這些局部變量將無法再從外部訪問;另外閉包還有一個重要的特性,每次執(zhí)行至閉包定義處時都會構(gòu)造一個新的閉包,這個特性使得舊的閉包綁定的變量不會隨第二次調(diào)用外部函數(shù)而更改。所以閉包實際上不會被外部狀態(tài)影響,完全符合函數(shù)式風(fēng)格的要求。(這里有一個特例,Python3.x中,如果同一個作用域中定義了兩個閉包,由于可以修改外部變量,他們可以相互影響。)雖然閉包只有在作為參數(shù)和返回值時才能發(fā)揮它的真正威力,但閉包的支持仍然大大提升了生產(chǎn)率。2.4.作為參數(shù)如果你對OOP的模板方法模式很熟悉,相信你能很快速地學(xué)會將函數(shù)當(dāng)作參數(shù)傳遞。兩者大體是一致的,只是在這里,我們傳遞的是函數(shù)本身而不再是實現(xiàn)了某個接口的對象。
我們先來給前面定義的求和函數(shù)add熱熱身:printadd('三角形的樹','北極')與加法運算符不同,你一定很驚訝于答案是'三角函數(shù)'。這是一個內(nèi)置的彩蛋...bazinga!言歸正傳。我們的客戶有一個從0到4的列表:lst=range(5)#[0,1,2,3,4]雖然我們在上一小節(jié)里給了他一個加法器,但現(xiàn)在他仍然在為如何計算這個列表所有元素的和而苦惱。當(dāng)然,對我們而言這個任務(wù)輕松極了:amount=0fornuminlst:
amount=add(amount,num)這是一段典型的指令式風(fēng)格的代碼,一點問題都沒有,肯定可以得到正確的結(jié)果?,F(xiàn)在,讓我們試著用函數(shù)式的風(fēng)格重構(gòu)一下。首先可以預(yù)見的是求和這個動作是非常常見的,如果我們把這個動作抽象成一個單獨的函數(shù),以后需要對另一個列表求和時,就不必再寫一遍這個套路了:defsum_(lst):
amount=0
fornuminlst:
amount=add(amount,num)
returnamount
printsum_(lst)還能繼續(xù)。sum_函數(shù)定義了這樣一種流程:
1.使用初始值與列表的第一個元素相加;
2.使用上一次相加的結(jié)果與列表的下一個元素相加;
3.重復(fù)第二步,直到列表中沒有更多元素;
4.將最后一次相加的結(jié)果返回。如果現(xiàn)在需要求乘積,我們可以寫出類似的流程——只需要把相加換成相乘就可以了:defmultiply(lst):
product=1
fornuminlst:
product=product*num
returnproduct除了初始值換成了1以及函數(shù)add換成了乘法運算符,其他的代碼全部都是冗余的。我們?yōu)槭裁床话堰@個流程抽象出來,而將加法、乘法或者其他的函數(shù)作為參數(shù)傳入呢?defreduce_(function,lst,initial):
result=initial
fornuminlst:
result=function(result,num)
returnresult
printreduce_(add,lst,0)現(xiàn)在,想要算出乘積,可以這樣做:printreduce_(lambdax,y:x*y,lst,1)那么,如果想要利用reduce_找出列表中的最大值,應(yīng)該怎么做呢?請自行思考:)雖然有模板方法這樣的設(shè)計模式,但那樣的復(fù)雜度往往使人們更情愿到處編寫循環(huán)。將函數(shù)作為參數(shù)完全避開了模板方法的復(fù)雜度。Python有一個內(nèi)建函數(shù)reduce,完整實現(xiàn)并擴展了reduce_的功能。本文稍后的部分包含了有用的內(nèi)建函數(shù)的介紹。請注意我們的目的是沒有循環(huán),使用函數(shù)替代循環(huán)是函數(shù)式風(fēng)格區(qū)別于指令式風(fēng)格的最顯而易見的特征。*像Python這樣構(gòu)建于類C語言之上的函數(shù)式語言,由于語言本身提供了編寫循環(huán)代碼的能力,內(nèi)置函數(shù)雖然提供函數(shù)式編程的接口,但一般在內(nèi)部還是使用循環(huán)實現(xiàn)的。同樣的,如果發(fā)現(xiàn)內(nèi)建函數(shù)無法滿足你的循環(huán)需求,不妨也封裝它,并提供一個接口。2.5.作為返回值將函數(shù)返回通常需要與閉包一起使用(即返回一個閉包)才能發(fā)揮威力。我們先看一個函數(shù)的定義:defmap_(function,lst):
result=[]
foriteminlst:
result.append(function(item))
returnresult函數(shù)map_封裝了最常見的一種迭代:對列表中的每個元素調(diào)用一個函數(shù)。map_需要一個函數(shù)參數(shù),并將每次調(diào)用的結(jié)果保存在一個列表中返回。這是指令式的做法,當(dāng)你知道了列表解析(listcomprehension)后,會有更好的實現(xiàn)。這里我們先略過map_的蹩腳實現(xiàn)而只關(guān)注它的功能。對于上一節(jié)中的lst,你可能發(fā)現(xiàn)最后求乘積結(jié)果始終是0,因為lst中包含了0。為了讓結(jié)果看起來足夠大,我們來使用map_為lst中的每個元素加1:lst=map_(lambdax:add(1,x),lst)printreduce_(lambdax,y:x*y,lst,1)答案是120,這還遠遠不夠大。再來:lst=map_(lambdax:add(10,x),lst)printreduce_(lambdax,y:x*y,lst,1)囧,事實上我真的沒有想到答案會是360360,我發(fā)誓沒有收周鴻祎任何好處。現(xiàn)在回頭看看我們寫的兩個lambda表達式:相似度超過90%,絕對可以使用抄襲來形容。而問題不在于抄襲,在于多寫了很多字符有木有?如果有一個函數(shù),根據(jù)你指定的左操作數(shù),能生成一個加法函數(shù),用起來就像這樣:#add_to(10)返回一個函數(shù),這個函數(shù)接受一個參數(shù)并加上10后返回lst=map_(add_to(10),lst)寫起來應(yīng)該會舒服不少。下面是函數(shù)add_to的實現(xiàn):defadd_to(n):
returnlambdax:add(n,x)通過為已經(jīng)存在的某個函數(shù)指定數(shù)個參數(shù),生成一個新的函數(shù),這個函數(shù)只需要傳入剩余未指定的參數(shù)就能實現(xiàn)原函數(shù)的全部功能,這被稱為偏函數(shù)。Python內(nèi)置的functools模塊提供了一個函數(shù)partial,可以為任意函數(shù)生成偏函數(shù):functools.partial(func[,*args][,**keywords])你需要指定要生成偏函數(shù)的函數(shù)、并且指定數(shù)個參數(shù)或者命名參數(shù),然后partial將返回這個偏函數(shù);不過嚴格的說partial返回的不是函數(shù),而是一個像函數(shù)一樣可直接調(diào)用的對象,當(dāng)然,這不會影響它的功能。另外一個特殊的例子是裝飾器。裝飾器用于增強甚至干脆改變原函數(shù)的功能,我曾寫過一篇文檔介紹裝飾器,地址在這里:\o"/huxi/archive/2011/03/01/1967600.html"/huxi/archive/2011/03/01/1967600.html。*題外話,單就例子中的這個功能而言,在一些其他的函數(shù)式語言中(例如Scala)可以使用名為柯里化(Currying)的技術(shù)實現(xiàn)得更優(yōu)雅??吕锘前呀邮芏鄠€參數(shù)的函數(shù)變換成接受一個單一參數(shù)(最初函數(shù)的第一個參數(shù))的函數(shù),并且返回接受余下的參數(shù)而且返回結(jié)果的新函數(shù)的技術(shù)。如下的偽代碼所示:#不是真實的代碼defadd(x)(y):#柯里化
returnx+y
lst=map_(add(10),lst)通過將add函數(shù)柯里化,使得add接受第一個參數(shù)x,并返回一個接受第二個參數(shù)y的函數(shù),調(diào)用該函數(shù)與前文中的add_to完全相同(返回x+y),且不再需要定義add_to??瓷先ナ遣皇歉忧逅??遺憾的是Python并不支持柯里化。2.6.部分內(nèi)建函數(shù)介紹reduce(function,iterable[,initializer])
這個函數(shù)的主要功能與我們定義的reduce_相同。需要補充兩點:
它的第二個參數(shù)可以是任何可迭代的對象(實現(xiàn)了__iter__()方法的對象);
如果不指定第三個參數(shù),則第一次調(diào)用function將使用iterable的前兩個元素作為參數(shù)。
由reduce和一些常見的function組合成了下面列出來的內(nèi)置函數(shù):all(iterable)==reduce(lambdax,y:bool(xandy),iterable)any(iterable)==reduce(lambdax,y:bool(xory),iterable)max(iterable[,args...][,key])==reduce(lambdax,y:xifkey(x)>key(y)elsey,iterable_and_args)min(iterable[,args...][,key])==reduce(lambdax,y:xifkey(x)<key(y)elsey,iterable_and_args)sum(iterable[,start])==reduce(lambdax,y:x+y,iterable,start)map(function,iterable,...)
這個函數(shù)的主要功能與我們定義的map_相同。需要補充一點:
map還可以接受多個iterable作為參數(shù),在第n次調(diào)用function時,將使用iterable1[n],iterable2[n],...作為參數(shù)。filter(function,iterable)
這個函數(shù)的功能是過濾出iterable中所有以元素自身作為參數(shù)調(diào)用function時返回True或bool(返回值)為True的元素并以列表返回,與系列第一篇中的my_filter函數(shù)相同。zip(iterable1,iterable2,...)
這個函數(shù)返回一個列表,每個元素都是一個元組,包含(iterable1[n],iterable2[n],...)。
例如:zip([1,2],[3,4])-->[(1,3),(2,4)]
如果參數(shù)的長度不一致,將在最短的序列結(jié)束時結(jié)束;如果不提供參數(shù),將返回空列表。除此之外,你還可以使用本文2.5節(jié)中提到的functools.partial()為這些內(nèi)置函數(shù)創(chuàng)建常用的偏函數(shù)。另外,pypi上有一個名為functional的模塊,除了這些內(nèi)建函數(shù)外,還額外提供了更多的有意思的函數(shù)。但由于使用的場合并不多,并且需要額外安裝,在本文中就不介紹了。但我仍然推薦大家下載這個模塊的純Python實現(xiàn)的源代碼看看,開闊思維嘛。里面的函數(shù)都非常短,源文件總共只有300行不到,地址在這里:/pypi/functional3.迭代器3.1.迭代器(Iterator)概述迭代器是訪問集合內(nèi)元素的一種方式。迭代器對象從集合的第一個元素開始訪問,直到所有的元素都被訪問一遍后結(jié)束。迭代器不能回退,只能往前進行迭代。這并不是什么很大的缺點,因為人們幾乎不需要在迭代途中進行回退操作。迭代器也不是線程安全的,在多線程環(huán)境中對可變集合使用迭代器是一個危險的操作。但如果小心謹慎,或者干脆貫徹函數(shù)式思想堅持使用不可變的集合,那這也不是什么大問題。對于原生支持隨機訪問的數(shù)據(jù)結(jié)構(gòu)(如tuple、list),迭代器和經(jīng)典for循環(huán)的索引訪問相比并無優(yōu)勢,反而丟失了索引值(可以使用內(nèi)建函數(shù)enumerate()找回這個索引值,這是后話)。但對于無法隨機訪問的數(shù)據(jù)結(jié)構(gòu)(比如set)而言,迭代器是唯一的訪問元素的方式。迭代器的另一個優(yōu)點就是它不要求你事先準(zhǔn)備好整個迭代過程中所有的元素。迭代器僅僅在迭代至某個元素時才計算該元素,而在這之前或之后,元素可以不存在或者被銷毀。這個特點使得它特別適合用于遍歷一些巨大的或是無限的集合,比如幾個G的文件,或是斐波那契數(shù)列等等。這個特點被稱為延遲計算或惰性求值(Lazyevaluation)。迭代器更大的功勞是提供了一個統(tǒng)一的訪問集合的接口。只要是實現(xiàn)了__iter__()方法的對象,就可以使用迭代器進行訪問。3.2.使用迭代器使用內(nèi)建的工廠函數(shù)iter(iterable)可以獲取迭代器對象:>>>lst=range(2)>>>it=iter(lst)>>>it<listiteratorobjectat0x00BB62F0>使用迭代器的next()方法可以訪問下一個元素:>>>it.next()0如果是Python2.6+,還有內(nèi)建函數(shù)next(iterator)可以完成這一功能:>>>next(it)1如何判斷迭代器還有更多的元素可以訪問呢?Python里的迭代器并沒有提供類似has_next()這樣的方法。
那么在這個例子中,我們已經(jīng)訪問到了最后一個元素1,再使用next()方法會怎樣呢?>>>it.next()Traceback(mostrecentcalllast):
File"<stdin>",line1,in<module>StopIterationPython遇到這樣的情況時將會拋出StopIteration異常。事實上,Python正是根據(jù)是否檢查到這個異常來決定是否停止迭代的。
這種做法與迭代前手動檢查是否越界相比各有優(yōu)點。但Python的做法總有一些利用異常進行流程控制的嫌疑。了解了這些情況以后,我們就能使用迭代器進行遍歷了。it=iter(lst)try:
whileTrue:
val=it.next()
printvalexceptStopIteration:
pass實際上,因為迭代操作如此普遍,Python專門將關(guān)鍵字for用作了迭代器的語法糖。在for循環(huán)中,Python將自動調(diào)用工廠函數(shù)iter()獲得迭代器,自動調(diào)用next()獲取元素,還完成了檢查StopIteration異常的工作。上述代碼可以寫成如下的形式,你一定非常熟悉:forvalinlst:
printval首先Python將對關(guān)鍵字in后的對象調(diào)用iter函數(shù)獲取迭代器,然后調(diào)用迭代器的next方法獲取元素,直到拋出StopIteration異常。對迭代器調(diào)用iter函數(shù)時將返回迭代器自身,所以迭代器也可以用于for語句中,不需要特殊處理。常用的幾個內(nèi)建數(shù)據(jù)結(jié)構(gòu)tuple、list、set、dict都支持迭代器,字符串也可以使用迭代操作。你也可以自己實現(xiàn)一個迭代器,如上所述,只需要在類的__iter__方法中返回一個對象,這個對象擁有一個next()方法,這個方法能在恰當(dāng)?shù)臅r候拋出StopIteration異常即可。但是需要自己實現(xiàn)迭代器的時候不多,即使需要,使用生成器會更輕松。下一篇我們將討論生成器的部分。*異常并不是非拋出不可的,不拋出該異常的迭代器將進行無限迭代,某些情況下這樣的迭代器很有用。這種情況下,你需要自己判斷元素并中止,否則就死循環(huán)了!使用迭代器的循環(huán)可以避開索引,但有時候我們還是需要索引來進行一些操作的。這時候內(nèi)建函數(shù)enumerate就派上用場咯,它能在iter函數(shù)的結(jié)果前加上索引,以元組返回,用起來就像這樣:foridx,eleinenumerate(lst):
printidx,ele3.3.生成器表達式(Generatorexpression)和列表解析(ListComprehension)絕大多數(shù)情況下,遍歷一個集合都是為了對元素應(yīng)用某個動作或是進行篩選。如果看過本文的第二部分,你應(yīng)該還記得有內(nèi)建函數(shù)map和filter提供了這些功能,但Python仍然為這些操作提供了語言級的支持。(x+1forxinlst)#生成器表達式,返回迭代器。外部的括號可在用于參數(shù)時省略。[x+1forxinlst]#列表解析,返回list如你所見,生成器表達式和列表解析(注:這里的翻譯有很多種,比如列表展開、列表推導(dǎo)等等,指的是同一個意思)的區(qū)別很小,所以人們提到這個特性時,簡單起見往往只描述成列表解析。然而由于返回迭代器時,并不是在一開始就計算所有的元素,這樣能得到更多的靈活性并且可以避開很多不必要的計算,所以除非你明確希望返回列表,否則應(yīng)該始終使用生成器表達式。接下來的文字里我就不區(qū)分這兩種形式了:)你也可以為列表解析提供if子句進行篩選:(x+1forxinlstifx!=0)或者提供多條for子句進行嵌套循環(huán),嵌套次序就是for子句的順序:((x,y)forxinrange(3)foryinrange(x))列表解析就是鮮明的Pythonic。我常遇到兩個使用列表解析的問題,本應(yīng)歸屬于最佳實踐,但這兩個問題非常典型,所以不妨在這里提一下:第一個問題是,因為對元素應(yīng)用的動作太復(fù)雜,不能用一個表達式寫出來,所以不使用列表解析。這是典型的思想沒有轉(zhuǎn)變的例子,如果我們將動作封裝成函數(shù),那不就是一個表達式了么?第二個問題是,因為if子句里的條件需要計算,同時結(jié)果也需要進行同樣的計算,不希望計算兩遍,就像這樣:(x.doSomething()forxinlstifx.doSomething()>0)這樣寫確實很糟糕,但組合一下列表解析即可解決:(xforxin(y.doSomething()foryinlst)ifx>0)內(nèi)部的列表解析變量其實也可以用x,但為清晰起見我們改成了y?;蛘吒宄?,可以寫成兩個表達式:tmp=(x.doSomething()forxinlst)(xforxintmpifx>0)列表解析可以替代絕大多數(shù)需要用到map和filter的場合,可能正因為此,著名的靜態(tài)檢查工具pylint將map和filter的使用列為了警告。3.4.相關(guān)的庫Python內(nèi)置了一個模塊itertools,包含了很多函數(shù)用于creatingiteratorsforefficientlooping(創(chuàng)建更有效率的循環(huán)迭代器),這說明很是霸氣,這一小節(jié)就來瀏覽一遍這些函數(shù)并留下印象吧,需要這些功能的時候隱約記得這里面有就好。這一小節(jié)的內(nèi)容翻譯自itertools模塊官方文檔。3.4.1.無限迭代count(start,[step])
從start開始,以后每個元素都加上step。step默認值為1。
count(10)-->1011121314...cycle(p)
迭代至序列p的最后一個元素后,從p的第一個元素重新開始。
cycle('ABCD')-->ABCDABCD...repeat(elem[,n])
將elem重復(fù)n次。如果不指定n,則無限重復(fù)。
repeat(10,3)-->1010103.4.2.在最短的序列參數(shù)終止時停止迭代chain(p,q,...)
迭代至序列p的最后一個元素后,從q的第一個元素開始,直到所有序列終止。
chain('ABC','DEF')-->ABCDEFcompress(data,selectors)
如果bool(selectors[n])為True,則next()返回data[n],否則跳過data[n]。
compress('ABCDEF',[1,0,1,0,1,1])-->ACEFdropwhile(pred,seq)
當(dāng)pred對seq[n]的調(diào)用返回False時才開始迭代。
dropwhile(lambdax:x<5,[1,4,6,4,1])-->641takewhile(pred,seq)
dropwhile的相反版本。
takewhile(lambdax:x<5,[1,4,6,4,1])-->14ifilter(pred,seq)
內(nèi)建函數(shù)filter的迭代器版本。
ifilter(lambdax:x%2,range(10))-->13579ifilterfalse(pred,seq)
ifilter的相反版本。
ifilterfalse(lambdax:x%2,range(10))-->02468imap(func,p,q,...)
內(nèi)建函數(shù)map的迭代器版本。
imap(pow,(2,3,10),(5,2,3))-->3291000starmap(func,seq)
將seq的每個元素以變長參數(shù)(*args)的形式調(diào)用func。
starmap(pow,[(2,5),(3,2),(10,3)])-->3291000izip(p,q,...)
內(nèi)建函數(shù)zip的迭代器版本。
izip('ABCD','xy')-->AxByizip_longest(p,q,...,fillvalue=None)
izip的取最長序列的版本,短序列將填入fillvalue。
izip_longest('ABCD','xy',fillvalue='-')-->AxByC-D-tee(it,n)
返回n個迭代器it的復(fù)制迭代器。groupby(iterable[,keyfunc])
這個函數(shù)功能類似于SQL的分組。使用groupby前,首先需要使用相同的keyfunc對iterable進行排序,比如調(diào)用內(nèi)建的sorted函數(shù)。然后,groupby返回迭代器,每次迭代的元素是元組(key值,iterable中具有相同key值的元素的集合的子迭代器)?;蛟S看看Python的排序指南對理解這個函數(shù)有幫助。
groupby([0,0,0,1,1,1,2,2,2])-->(0,(000))(1,(111))(2,(222))3.4.3.組合迭代器product(p,q,...[repeat=1])
笛卡爾積。
product('ABCD',repeat=2)-->AAABACADBABBBCBDCACBCCCDDADBDCDDpermutations(p[,r])
去除重復(fù)的元素。
permutations('ABCD',2)-->ABACADBABCBDCACBCDDADBDCcombinations(p,r)
排序后去除重復(fù)的元素。
combinations('ABCD',2)-->ABACADBCBDCDcombinations_with_replacement()
排序后,包含重復(fù)元素。
combinations_with_replacement('ABCD',2)-->AAABACADBBBCBDCCCDDD4.生成器(generator)4.1.生成器簡介首先請確信,生成器就是一種迭代器。生成器擁有next方法并且行為與迭代器完全相同,這意味著生成器也可以用于Python的for循環(huán)中。另外,對于生成器的特殊語法支持使得編寫一個生成器比自定義一個常規(guī)的迭代器要簡單不少,所以生成器也是最常用到的特性之一。從Python2.5開始,[PEP342:通過增強生成器實現(xiàn)協(xié)同程序]的實現(xiàn)為生成器加入了更多的特性,這意味著生成器還可以完成更多的工作。這部分我們會在稍后的部分介紹。4.2.生成器函數(shù)4.2.1.使用生成器函數(shù)定義生成器如何獲取一個生成器?首先來看一小段代碼:>>>defget_0_1_2():...
yield0...
yield1...
yield2...>>>get_0_1_2<functionget_0_1_2at0x00B2CB70>我們定義了一個函數(shù)get_0_1_2,并且可以查看到這確實是函數(shù)類型。但與一般的函數(shù)不同的是,get_0_1_2的函數(shù)體內(nèi)使用了關(guān)鍵字yield,這使得get_0_1_2成為了一個生成器函數(shù)。生成器函數(shù)的特性如下:調(diào)用生成器函數(shù)將返回一個生成器;>>>generator=get_0_1_2()>>>generator<generatorobjectget_0_1_2at0x00B1C7D8>第一次調(diào)用生成器的next方法時,生成器才開始執(zhí)行生成器函數(shù)(而不是構(gòu)建生成器時),直到遇到y(tǒng)ield時暫停執(zhí)行(掛起),并且yield的參數(shù)將作為此次next方法的返回值;>>>generator.next()0之后每次調(diào)用生成器的next方法,生成器將從上次暫停執(zhí)行的位置恢復(fù)執(zhí)行生成器函數(shù),直到再次遇到y(tǒng)ield時暫停,并且同樣的,yield的參數(shù)將作為next方法的返回值;>>>generator.next()1>>>generator.next()2如果當(dāng)調(diào)用next方法時生成器函數(shù)結(jié)束(遇到空的return語句或是到達函數(shù)體末尾),則這次next方法的調(diào)用將拋出StopIteration異常(即for循環(huán)的終止條件);>>>generator.next()Traceback(mostrecentcalllast):
File"<stdin>",line1,in<module>StopIteration生成器函數(shù)在每次暫停執(zhí)行時,函數(shù)體內(nèi)的所有變量都將被封存(freeze)在生成器中,并將在恢復(fù)執(zhí)行時還原,并且類似于閉包,即使是同一個生成器函數(shù)返回的生成器,封存的變量也是互相獨立的。
我們的小例子中并沒有用到變量,所以這里另外定義一個生成器來展示這個特點:>>>deffibonacci():...
a=b=1...
yielda...
yieldb...
whileTrue:...
a,b=b,a+b...
yieldb...>>>fornuminfibonacci():...
ifnum>100:break...
printnum,...1123581321345589看到whileTrue可別太吃驚,因為生成器可以掛起,所以是延遲計算的,無限循環(huán)并沒有關(guān)系。這個例子中我們定義了一個生成器用于獲取斐波那契數(shù)列。4.2.2.生成器函數(shù)的FAQ接下來我們來討論一些關(guān)于生成器的有意思的話題。你的例子里生成器函數(shù)都沒有參數(shù),那么生成器函數(shù)可以帶參數(shù)嗎?
當(dāng)然可以啊親,而且它支持函數(shù)的所有參數(shù)形式。要知道生成器函數(shù)也是函數(shù)的一種:)>>>defcounter(start=0):...
whileTrue:...
yieldstart...
start+=1...這是一個從指定數(shù)開始的計數(shù)器。既然生成器函數(shù)也是函數(shù),那么它可以使用return輸出返回值嗎?
不行的親,是這樣的,生成器函數(shù)已經(jīng)有默認的返回值——生成器了,你不能再另外給一個返回值;對,即使是returnNone也不行。但是它可以使用空的return語句結(jié)束。如果你堅持要為它指定返回值,那么Python將在定義的位置贈送一個語法錯誤異常,就像這樣:>>>defi_wanna_return():...
yieldNone...
returnNone...
File"<stdin>",line3SyntaxError:'return'withargumentinsidegenerator好吧,那人家需要確保釋放資源,需要在try...finally中yield,這會是神馬情況?(我就是想玩你)我在finally中還yield了一次!
Python會在真正離開try...finally時再執(zhí)行finally中的代碼,而這里遺憾地告訴你,暫停不算哦!所以結(jié)局你也能猜到吧!>>>defplay_u():...
try:...
yield1...
yield2...
yield3...
finally:...
yield0...>>>forvalinplay_u():printval,...1230*這與return的情況不同。return是真正的離開代碼塊,所以會在return時立刻執(zhí)行finally子句。
*另外,“在帶有finally子句的try塊中yield”定義在PEP342中,這意味著只有Python2.5以上版本才支持這個語法,在Python2.4以下版本中會得到語法錯誤異常。如果我需要在生成器的迭代過程中接入另一個生成器的迭代怎么辦?寫成下面這樣好傻好天真。。>>>defsub_generator():...
yield1...
yield2...
forvalincounter(10):yieldval...這種情況的語法改進已經(jīng)被定義在[PEP380:委托至子生成器的語法]中,據(jù)說會在Python3.3中實現(xiàn),屆時也可能回饋到2.x中。實現(xiàn)后,就可以這么寫了:>>>defsub_generator():...
yield1...
yield2...
yieldfromcounter(10)
File"<stdin>",line4
yieldfromcounter(10)
^SyntaxError:invalidsyntax看到語法錯誤木有?現(xiàn)在我們還是天真一點吧~4.3.協(xié)同程序(coroutine)協(xié)同程序(協(xié)程)一般來說是指這樣的函數(shù):彼此間有不同的局部變量、指令指針,但仍共享全局變量;可以方便地掛起、恢復(fù),并且有多個入口點和出口點;多個協(xié)同程序間表現(xiàn)為協(xié)作運行,如A的運行過程中需要B的結(jié)果才能繼續(xù)執(zhí)行。協(xié)程的特點決定了同一時刻只能有一個協(xié)同程序正在運行(忽略多線程的情況)。得益于此,協(xié)程間可以直接傳遞對象而不需要考慮資源鎖、或是直接喚醒其他協(xié)程而不需要主動休眠,就像是內(nèi)置了鎖的線程。在符合協(xié)程特點的應(yīng)用場景,使用協(xié)程無疑比使用線程要更方便。從另一方面說,協(xié)程無法并發(fā)其實也將它的應(yīng)用場景限制在了一個很狹窄的范圍,這個特點使得協(xié)程更多的被拿來與常規(guī)函數(shù)進
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 個人倉儲租賃合同范本
- 水泵購買合同
- 綠色電子元器件生產(chǎn)合同
- 貨車轉(zhuǎn)讓合同協(xié)議書
- 中外合資經(jīng)營合同范本(建筑材料)
- 個人與公司租房合同樣本參考
- 專利技術(shù)合同轉(zhuǎn)讓合同
- 中小企業(yè)板塊上市證券合同范本
- 個人勞動合同范本:版
- 中保人壽子女教育婚嫁備用金保險合同細則(97版修訂)
- 2025年中國X線診斷設(shè)備行業(yè)市場發(fā)展前景及發(fā)展趨勢與投資戰(zhàn)略研究報告
- 2024版全文:中國2型糖尿病預(yù)防及治療指南
- 讀書心得《好老師征服后進生的14堂課》讀后感
- 公路工程施工安全應(yīng)急預(yù)案(4篇)
- 社會主義發(fā)展史(齊魯師范學(xué)院)知到智慧樹章節(jié)答案
- 2023年高考真題-地理(遼寧卷) 含解析
- 課程思政融入高職院校應(yīng)用文寫作課程教學(xué)路徑探析
- 2024全新鋼結(jié)構(gòu)安全培訓(xùn)
- 2025屆高三數(shù)學(xué)一輪復(fù)習(xí)-分段函數(shù)專項訓(xùn)練【含答案】
- 腰椎間盤突出癥課件(共100張課件)
- 《工程力學(xué)》課程教學(xué)大綱
評論
0/150
提交評論