在這篇文章里,你將學會什么是函數(shù)范式以及如何使用Python進行函數(shù)式編程僻弹。你也將了解列表推導和其它形式的推導阿浓。
函數(shù)范式
在命令式范式中,通過為計算機提供一系列指令然后執(zhí)行它們來完成任務蹋绽。在執(zhí)行這些指令時芭毙,可以改變某些狀態(tài)。例如卸耘,假設你最初將A設置為5退敦,然后更改A的值。這時在變量內(nèi)部值的意義上蚣抗,你改變了A的狀態(tài)侈百。
在函數(shù)式范式中,你不用告訴計算機做什么而是告訴他這個東西是什么翰铡。比如數(shù)字的最大公約數(shù)是什么钝域,從1到n的乘積是什么等等。
因此锭魔,變量不能變化例证。一旦你設置了一個變量,它就永遠保持這種狀態(tài)(注意迷捧,在純函數(shù)式語言中织咧,它們不是變量)。因此漠秋,函數(shù)式編程沒有副作用笙蒙。副作用指的是函數(shù)改變它自己以外的東西。讓我們看一些典型Python代碼的示例:
10分鐘學會python函數(shù)式編程
這段代碼的輸出是5庆锦。在函數(shù)式范式中捅位,改變變量是一個很大的禁忌,并且具有影響其范圍之外事物的功能也是一個很大的禁忌。函數(shù)唯一能做的就是計算一些東西并將其作為結(jié)果返回绿渣。
現(xiàn)在你可能會想:“沒有變量朝群,沒有副作用?為什么這樣好中符?“這個問題問得好姜胖,我相信大多數(shù)人對此感到疑惑。
如果使用相同的參數(shù)調(diào)用函數(shù)兩次淀散,則保證返回相同的結(jié)果右莱。如果你已經(jīng)學習了數(shù)學函數(shù),你就會知道這個好處档插。這稱為參照透明度慢蜓。由于函數(shù)沒有副作用,如果你正在構(gòu)建一個計算某些事情的程序郭膛,你可以加速程序晨抡。如果每次調(diào)用func(2)都返回3,我們可以將它存儲在表中则剃,這可以防止程序重復運行相同的功能耘柱。
通常,在函數(shù)式編程中棍现,我們不使用循環(huán)调煎。我們使用遞歸。遞歸是一個數(shù)學概念己肮,通常意味著“自我調(diào)用”士袄。使用遞歸函數(shù),該函數(shù)將其自身作為子函數(shù)重復調(diào)用谎僻。這是Python中遞歸函數(shù)的一個很好的例子:
10分鐘學會python函數(shù)式編程
有些編程語言也具有惰性娄柳。這意味著他們直到最后一秒才計算或做任何事情。如果你編寫一些代碼來執(zhí)行2 + 2艘绍,函數(shù)程序只會在你真正需要使用結(jié)果時計算出來西土。我們很快就會在Python中探索惰性。
Map
為了理解鞍盗,我們先來看看迭代是什么。通程纾可以迭代的對象是列表或數(shù)組般甲,但Python有許多不同的類型可以迭代。你甚至可以創(chuàng)建自己的對象鹅颊,這些對象可以通過實現(xiàn)魔術方法進行迭代敷存。魔術方法就像是一個API,可以幫助你的對象變得更加Pythonic。您需要實現(xiàn)2個魔術方法才能使對象成為可迭代的:
10分鐘學會python函數(shù)式編程
第一個魔術方法“iter”(注:這里是雙下劃線)返回迭代對象锚烦,這通常在循環(huán)開始時使用觅闽。”next“返回下一個對象涮俄。
讓我們快速進入一個終端調(diào)用上面的代碼:
10分鐘學會python函數(shù)式編程
運行將會打印出
10分鐘學會python函數(shù)式編程
在Python中蛉拙,迭代器是一個只有iter魔術方法的對象。這意味著您可以訪問對象中的位置彻亲,但不能遍歷該對象孕锄。一些對象將具有魔術方法next而不是iter魔術方法,例如集合(在本文后面討論)苞尝。對于本文畸肆,我們假設我們接觸的所有內(nèi)容都是可迭代的對象。
現(xiàn)在我們知道什么是可迭代對象了宙址,讓我們回到map函數(shù)轴脐。 map函數(shù)允許我們將函數(shù)應用于iterable中的每一項。 Map需要2個輸入抡砂,它們分別是要應用的函數(shù)和可迭代對象大咱。
10分鐘學會python函數(shù)式編程
假設我們有一個數(shù)字列表,如下所示:
10分鐘學會python函數(shù)式編程
我們想要對每個數(shù)字進行平方舀患,我們可以編寫如下代碼:
10分鐘學會python函數(shù)式編程
Python中函數(shù)式的函數(shù)是具有惰性的徽级。如果我們不使用“l(fā)ist”,該函數(shù)將存儲iterable的定義聊浅,而不是列表本身餐抢。我們需要明確告訴Python“把它變成一個列表”供我們使用。
在Python中突然從非惰性求值轉(zhuǎn)向惰性求值有點奇怪低匙。如果你在函數(shù)式思維方式中考慮得更多旷痕,而不是命令式思維方式,那么你最終會習慣它顽冶。
現(xiàn)在寫一個像“square(num)”這樣的普通函數(shù)雖然很好欺抗,但卻是不對的。我們必須定義一個完整的函數(shù)才能在map中使用它强重?好吧绞呈,我們可以使用lambda(匿名)函數(shù)在map中定義一個函數(shù)。
Lambda表達式
lambda表達式是一個只有一行的函數(shù)间景。舉個例子佃声,這個lambda表達式對給定的數(shù)字進行平方:
10分鐘學會python函數(shù)式編程
讓我們運行它:
10分鐘學會python函數(shù)式編程
這看起來不像一個函數(shù)嗎?
嗯倘要,這有點令人困惑圾亏,但可以解釋。我們將一些東西分配給變量“square”。那這個呢:
10分鐘學會python函數(shù)式編程
告訴Python這是一個lambda函數(shù)志鹃,輸入叫做x夭问。冒號之后的任何內(nèi)容都是您對輸入所做的操作,它會自動返回結(jié)果曹铃。
簡化我們的square程序到只有一行代碼缰趋,我們可以這樣做:
10分鐘學會python函數(shù)式編程
所以在lambda表達式中,所有參數(shù)都在左邊铛只,你要用它們做的東西在右邊埠胖。它有點亂。但事實是淳玩,編寫只有其他函數(shù)式程序員才能閱讀的代碼會有一定的樂趣直撤。此外告希,使用一個函數(shù)并將其轉(zhuǎn)換為一行代碼是非扯味啵酷的。
Reduce
Reduce是一個將迭代變成一個東西的函數(shù)遭庶。通常承匣,你可以在列表上使用reduce函數(shù)執(zhí)行計算以將其減少到一個數(shù)字蓖乘。 Reduce看起來像這樣:
10分鐘學會python函數(shù)式編程
我們經(jīng)常會使用lambda表達式作為函數(shù)。
列表的乘積是每個單獨的數(shù)字相乘韧骗。要做到這一點你將編寫如下代碼:
10分鐘學會python函數(shù)式編程
但是使用reduce你可以這樣寫:
10分鐘學會python函數(shù)式編程
獲得相同的功能嘉抒,代碼更短,并且在使用函數(shù)式編程的情況下更整潔袍暴。(注:reduce函數(shù)在Python3中已不是內(nèi)置函數(shù)些侍,需要從functools模塊中導入)
Filter
filter函數(shù)采用可迭代的方式,并過濾掉你在該可迭代中不需要的所有內(nèi)容政模。
通常岗宣,filter需要一個函數(shù)和一個列表。它將函數(shù)應用于列表中的每一項淋样,如果該函數(shù)返回True耗式,則不執(zhí)行任何操作。如果返回False趁猴,則從列表中刪除該項刊咳。
語法如下:
10分鐘學會python函數(shù)式編程
讓我們看一個小例子,沒有filter我們會寫:
10分鐘學會python函數(shù)式編程
使用filter儡司,可以這樣寫:
10分鐘學會python函數(shù)式編程
高階函數(shù)
高階函數(shù)可以將函數(shù)作為參數(shù)并返回函數(shù)芦缰。一個非常簡單的例子如下:
10分鐘學會python函數(shù)式編程
第二個返回函數(shù)的例子:
10分鐘學會python函數(shù)式編程
開頭我說過純函數(shù)式編程語言沒有變量。更高階的函數(shù)使這變得更容易枫慷。
Python中的所有函數(shù)都是一等公民。一等公民被定義為具有以下一個或多個特征:
在運行時創(chuàng)建
在數(shù)據(jù)結(jié)構(gòu)中分配變量或元素
作為函數(shù)的參數(shù)傳遞
作為函數(shù)的結(jié)果返回
Python中的所有函數(shù)都可以用作高階函數(shù)。
Partial application
Partial application(也稱為閉包)有點奇怪或听,但非程叫ⅲ酷。您可以在不提供所需的所有參數(shù)的情況下調(diào)用函數(shù)誉裆。讓我們在一個例子中看到這一點顿颅。我們想要創(chuàng)建一個函數(shù),它接受2個參數(shù)足丢,一個基數(shù)和一個指數(shù)粱腻,并返回指數(shù)冪的基數(shù),如下所示:
10分鐘學會python函數(shù)式編程
現(xiàn)在我們想要一個專用的平方函數(shù)斩跌,使用冪函數(shù)計算出數(shù)字的平方:
10分鐘學會python函數(shù)式編程
這有效绍些,但如果我們想要一個立方體功能呢?或者求四次方的功能呢耀鸦?我們可以繼續(xù)寫下它們嗎柬批?好吧,你可以袖订。但程序員很懶的氮帐。如果你一遍又一遍地重復同樣的事情,這表明有一種更快的方法來加快速度洛姑,這將使你不再重復上沐。我們可以在這里使用閉包。讓我們看一個使用閉包的square函數(shù)的示例:
10分鐘學會python函數(shù)式編程
是不是很酷楞艾!我們可以只使用1個參數(shù)來調(diào)用需要2個參數(shù)的函數(shù)参咙。
我們還可以使用一個循環(huán)來生成一個冪函數(shù),該函數(shù)實現(xiàn)從立方體一直到1000的冪产徊。
10分鐘學會python函數(shù)式編程
函數(shù)式編程不是pythonic
您可能已經(jīng)注意到了昂勒,我們想要在函數(shù)式編程中做的很多事情都圍繞著列表。除了reduce函數(shù)和閉包之外舟铜,您看到的所有函數(shù)都會生成列表戈盈。 Guido(Python之父)不喜歡Python中的函數(shù)式,因為Python已經(jīng)有了自己生成列表的方法谆刨。
如果你在Python的交互環(huán)境下寫入”import this“塘娶,你將會得到:
10分鐘學會python函數(shù)式編程
這是Python之禪。這是一首關于Pythonic意味著什么的詩痊夭。我們想要涉及的部分是:
There should be one — and preferably only one — obvious way to do it.(應該盡量找到一種刁岸,最好是唯一一種明顯的解決方案)
在Python中,map和filter可以執(zhí)行與列表推導(下面討論)相同的操作她我。這打破了Python之禪的一個規(guī)則虹曙,因此函數(shù)式編程的這些部分不被視為“pythonic”迫横。
另一個話題是Lambda。在Python中酝碳,lambda函數(shù)是一個普通函數(shù)矾踱。 Lambda是語法糖。這兩種說法是等價的疏哗。
10分鐘學會python函數(shù)式編程
普通函數(shù)可以執(zhí)行l(wèi)ambda函數(shù)可以執(zhí)行的所有操作呛讲,但它不能以相反的方式工作。 lambda函數(shù)不能完成普通函數(shù)可以執(zhí)行的所有操作返奉。
這是一個簡短的論證贝搁,為什么函數(shù)式編程不能很好地適應整個Python生態(tài)系統(tǒng)。你可能已經(jīng)注意到我之前提到了列表推導芽偏,我們現(xiàn)在將討論它們雷逆。
列表推導
前面,我提到過你可以用map或filter做的任何事情哮针,你可以用列表推導关面。列表推導是一種在Python中生成列表的方法。語法是:
10分鐘學會python函數(shù)式編程
讓我們對列表中的每個數(shù)字進行平方十厢,例如:
10分鐘學會python函數(shù)式編程
我們可以看到如何將函數(shù)應用于列表中的每一項等太。我們?nèi)绾螒胒ilter呢?看看前面的代碼:
10分鐘學會python函數(shù)式編程
我們可以將其轉(zhuǎn)換成一個列表推導蛮放,像這樣:
10分鐘學會python函數(shù)式編程
列表支持if這樣的語句缩抡。您不再需要將一百萬個函數(shù)應用于某些東西以獲得您想要的東西。事實上包颁,如果你想嘗試生成某種列表瞻想,那么使用列表推導看起來會更清晰,更容易娩嚼。如果我們想要將列表中每個0以下的數(shù)字平方怎么辦蘑险?有了lambda,map和filter你會寫:
10分鐘學會python函數(shù)式編程
這似乎很長很復雜岳悟。通過列表推導佃迄,它只是:
10分鐘學會python函數(shù)式編程
列表推導僅適用于列表。map,filter適合任何可迭代的對象贵少,那么這有什么用呢呵俏?你可以對你遇到的任何可迭代對象使用任何推導。
其他推導
你可以為任何可迭代對象創(chuàng)建一個推導滔灶。
可以使用推導生成任何可迭代的對象普碎。從Python 2.7開始,您甚至可以生成字典(hashmap)录平。
如果它是可迭代的麻车,則可以生成它缀皱。讓我們看一下最后一組的例子。
10分鐘學會python函數(shù)式編程
set是一個元素列表动猬,在該列表中沒有元素重復兩次唆鸡。
set中的元素沒有順序。
10分鐘學會python函數(shù)式編程
您可能會注意到set(集合)與dict(字典)具有相同的花括號枣察。 Python非常聰明。根據(jù)你是否為dict提供值燃逻,它會知道你是在寫dict推導還是set推導序目。
總結(jié)
函數(shù)式編程美觀而純粹。函數(shù)式代碼可以很干凈伯襟,但也可能很亂猿涨。一些Python程序員不喜歡Python中的函數(shù)式編程。但我認為姆怪,你應該在解決問題時叛赚,使用最佳工具。
(需要python資料的加我微信:xiangxiang19850217)