**:
這大概算是python最難啃的一塊骨頭吧。在我python生涯的這一年裡,我遇到了一些pythoner,他們毫無例外地完全不會使用函式式程式設計(有些人喜歡稱為pythonic),比如,從來不會傳遞函式,不知道lambda是什麼意思,知道列表展開但從來不知道用在**,對python不提供經典for迴圈感到無所適從,言談之中表現出對函式式風格的一種抗拒甚至厭惡。
我嘗試剖析這個問題,最終總結了這麼兩個原因:1、不想改變,認為現有的知識可以完成任務;2、對小眾語言的歧視,python目前在國內市場份額仍然很小很小,熟悉python風格用處不大。
函式式程式設計概述
1.1. 什麼是函式式程式設計?
函式式程式設計使用一系列的函式解決問題。函式僅接受輸入並產生輸出,不包含任何能影響產生輸出的內部狀態。任何情況下,使用相同的引數呼叫函式始終能產生同樣的結果。
在乙個函式式的程式中,輸入的資料「流過」一系列的函式,每乙個函式根據它的輸入產生輸出。函式式風格避免編寫有「邊界效應」(side effects)的函式:修改內部狀態,或者是其他無法反應在輸出上的變化。完全沒有邊界效應的函式被稱為「純函式式的」(purely functional)。避免邊界效應意味著不使用在程式執行時可變的資料結構,輸出只依賴於輸入。
可以認為函式式程式設計剛好站在了物件導向程式設計的對立面。物件通常包含內部狀態(字段),和許多能修改這些狀態的函式,程式則由不斷修改狀態構成;函式式程式設計則極力避免狀態改動,並通過在函式間傳遞資料流進行工作。但這並不是說無法同時使用函式式程式設計和物件導向程式設計,事實上,複雜的系統一般會採用物件導向技術建模,但混合使用函式式風格還能讓你額外享受函式式風格的優點。
1.2. 為什麼使用函式式程式設計?
函式式的風格通常被認為有如下優點:
邏輯可證
這是乙個學術上的優點:沒有邊界效應使得更容易從邏輯上證明程式是正確的(而不是通過測試)。
模組化
函式式程式設計推崇簡單原則,乙個函式只做一件事情,將大的功能拆分成盡可能小的模組。小的函式更易於閱讀和檢查錯誤。
元件化
小的函式更容易加以組合形成新的功能。
易於除錯
細化的、定義清晰的函式使得除錯更加簡單。當程式不正常執行時,每乙個函式都是檢查資料是否正確的介面,能更快速地排除沒有問題的**,定位到出現問題的地方。
易於測試
不依賴於系統狀態的函式無須在測試前構造測試樁,使得編寫單元測試更加容易。
更高的生產率
函式式程式設計產生的**比其他技術更少(往往是其他技術的一半左右),並且更容易閱讀和維護。
1.3. 如何辨認函式式風格?
支援函式式程式設計的語言通常具有如下特徵,大量使用這些特徵的**即可被認為是函式式的:
函式是一等公民
函式能作為引數傳遞,或者是作為返回值返回。這個特性使得模板方法模式非常易於編寫,這也促使了這個模式被更頻繁地使用。
以乙個簡單的集合排序為例,假設lst是乙個數集,並擁有乙個排序方法sort需要將如何確定順序作為引數。
如果函式不能作為引數,那麼lst的sort方法只能接受普通物件作為引數。這樣一來我們需要首先定義乙個介面,然後定義乙個實現該介面的類,最後將該類的乙個例項傳給sort方法,由sort呼叫這個例項的compare方法,就像這樣:
1 2
3 4
5 6
7 8
9def my_filter(lst, minval):
helper= greater_than_helper(minval)
return filter(helper.is_greater_than, lst)
請注意我們現在已經為過濾功能編寫了乙個函式my_filter。如你所見,我們需要在別的地方(此例中是類greater_than_helper)持有另乙個運算元minval。
如果支援閉包,因為閉包可以直接使用外部作用域的變數,我們就不再需要greater_than_helper了:
1 2
def my_filter(lst, minval):
return filter(lambda n: n > minval, lst)
可見,閉包在不影響可讀性的同時也省下了不少**量。
函式式程式語言都提供了對閉包的不同程度的支援。在python 2.x中,閉包無法修改繫結變數的值,所有修改繫結變數的行為都被看成新建了乙個同名的區域性變數並將繫結變數隱藏。python 3.x中新加入了乙個關鍵字 nonlocal 以支援修改繫結變數。但不管支援程度如何,你始終可以訪問(讀取)繫結變數。
內建的不可變資料結構
為了避開邊界效應,不可變的資料結構是函式式程式設計中不可或缺的部分。不可變的資料結構保證資料的一致性,極大地降低了排查問題的難度。
例如,python中的元組(tuple)就是不可變的,所有對元組的操作都不能改變元組的內容,所有試圖修改元組內容的操作都會產生乙個異常。
函式式程式語言一般會提供資料結構的兩種版本(可變和不可變),並推薦使用不可變的版本。
遞迴 遞迴是另一種取代迴圈的方法。遞迴其實是函式式程式設計很常見的形式,經常可以在一些演算法中見到。但之所以放到最後,是因為實際上我們一般很少用到遞迴。如果乙個遞迴無法被編譯器或直譯器優化,很容易就會產生棧溢位;另一方面複雜的遞迴往往讓人感覺迷惑,不如迴圈清晰,所以眾多最佳實踐均指出使用迴圈而非遞迴。
這一系列短文中都不會關注遞迴的使用。
Python函式式程式設計指南(一) 概述
這大概算是python最難啃的一塊骨頭吧。在我python生涯的這一年裡,我遇到了一些pythoner,他們毫無例外地完全不會使用函式式程式設計 有些人喜歡稱為pythonic 比如,從來不會傳遞函式,不知道lambda是什麼意思,知道列表展開但從來不知道用在 對python不提供經典for迴圈感到...
Python函式式程式設計指南(一) 概述
這大概算是python最難啃的一塊骨頭吧。在我python生涯的這一年裡,我遇到了一些pythoner,他們毫無例外地完全不會使用函式式程式設計 有些人喜歡稱為pythonic 比如,從來不會傳遞函式,不知道lambda是什麼意思,知道列表展開但從來不知道用在 對python不提供經典for迴圈感到...
Python函式式程式設計指南(一) 概述
這大概算是python最難啃的一塊骨頭吧。在我python生涯的這一年裡,我遇到了一些pythoner,他們毫無例外地完全不會使用函式式程式設計 有些人喜歡稱為pythonic 比如,從來不會傳遞函式,不知道lambda是什麼意思,知道列表展開但從來不知道用在 對python不提供經典for迴圈感到...