在這篇文章裏,你將學會什麼是函數範式以及如何使用Python進行函數式編程。你也將瞭解列表推導和其它形式的推導。python
函數範式程序員
在命令式範式中,經過爲計算機提供一系列指令而後執行它們來完成任務。在執行這些指令時,能夠改變某些狀態。例如,假設你最初將A設置爲5,而後更改A的值。這時在變量內部值的意義上,你改變了A的狀態。編程
在函數式範式中,你不用告訴計算機作什麼而是告訴他這個東西是什麼。好比數字的最大公約數是什麼,從1到n的乘積是什麼等等。數組
所以,變量不能變化。一旦你設置了一個變量,它就永遠保持這種狀態(注意,在純函數式語言中,它們不是變量)。所以,函數式編程沒有反作用。反作用指的是函數改變它本身之外的東西。讓咱們看一些典型Python代碼的示例:數據結構
這段代碼的輸出是5。在函數式範式中,改變變量是一個很大的禁忌,而且具備影響其範圍以外事物的功能也是一個很大的禁忌。函數惟一能作的就是計算一些東西並將其做爲結果返回。閉包
如今你可能會想:「沒有變量,沒有反作用?爲何這樣好?「這個問題問得好,我相信大多數人對此感到疑惑。app
若是使用相同的參數調用函數兩次,則保證返回相同的結果。若是你已經學習了數學函數,你就會知道這個好處。這稱爲參照透明度。因爲函數沒有反作用,若是你正在構建一個計算某些事情的程序,你能夠加速程序。若是每次調用func(2)都返回3,咱們能夠將它存儲在表中,這能夠防止程序重複運行相同的功能。編程語言
一般,在函數式編程中,咱們不使用循環。咱們使用遞歸。遞歸是一個數學概念,一般意味着「自我調用」。使用遞歸函數,該函數將其自身做爲子函數重複調用。這是Python中遞歸函數的一個很好的例子:ide
有些編程語言也具備惰性。這意味着他們直到最後一秒才計算或作任何事情。若是你編寫一些代碼來執行2 + 2,函數程序只會在你真正須要使用結果時計算出來。咱們很快就會在Python中探索惰性。函數式編程
Map
爲了理解,咱們先來看看迭代是什麼。一般能夠迭代的對象是列表或數組,但Python有許多不一樣的類型能夠迭代。你甚至能夠建立本身的對象,這些對象能夠經過實現魔術方法進行迭代。魔術方法就像是一個API,能夠幫助你的對象變得更加Pythonic。您須要實現2個魔術方法才能使對象成爲可迭代的:
第一個魔術方法「__iter__」(注:這裏是雙下劃線)返回迭代對象,這一般在循環開始時使用。」__next__「返回下一個對象。
讓咱們快速進入一個終端調用上面的代碼:
運行將會打印出
在Python中,迭代器是一個只有__iter__魔術方法的對象。這意味着您能夠訪問對象中的位置,但不能遍歷該對象。一些對象將具備魔術方法__next__而不是__iter__魔術方法,例如集合(在本文後面討論)。對於本文,咱們假設咱們接觸的全部內容都是可迭代的對象。
如今咱們知道什麼是可迭代對象了,讓咱們回到map函數。 map函數容許咱們將函數應用於iterable中的每一項。 Map須要2個輸入,它們分別是要應用的函數和可迭代對象。
假設咱們有一個數字列表,以下所示:
咱們想要對每一個數字進行平方,咱們能夠編寫以下代碼:
Python中函數式的函數是具備惰性的。若是咱們不使用「list」,該函數將存儲iterable的定義,而不是列表自己。咱們須要明確告訴Python「把它變成一個列表」供咱們使用。
在Python中忽然從非惰性求值轉向惰性求值有點奇怪。若是你在函數式思惟方式中考慮得更多,而不是命令式思惟方式,那麼你最終會習慣它。
如今寫一個像「square(num)」這樣的普通函數雖然很好,但倒是不對的。咱們必須定義一個完整的函數才能在map中使用它?好吧,咱們可使用lambda(匿名)函數在map中定義一個函數。
Lambda表達式
lambda表達式是一個只有一行的函數。舉個例子,這個lambda表達式對給定的數字進行平方:
讓咱們運行它:
這看起來不像一個函數嗎?
嗯,這有點使人困惑,但能夠解釋。咱們將一些東西分配給變量「square」。那這個呢:
告訴Python這是一個lambda函數,輸入叫作x。冒號以後的任何內容都是您對輸入所作的操做,它會自動返回結果。
簡化咱們的square程序到只有一行代碼,咱們能夠這樣作:
因此在lambda表達式中,全部參數都在左邊,你要用它們作的東西在右邊。它有點亂。但事實是,編寫只有其餘函數式程序員才能閱讀的代碼會有必定的樂趣。此外,使用一個函數並將其轉換爲一行代碼是很是酷的。
Reduce
Reduce是一個將迭代變成一個東西的函數。一般,你能夠在列表上使用reduce函數執行計算以將其減小到一個數字。 Reduce看起來像這樣:
咱們常常會使用lambda表達式做爲函數。
列表的乘積是每一個單獨的數字相乘。要作到這一點你將編寫以下代碼:
可是使用reduce你能夠這樣寫:
得到相同的功能,代碼更短,而且在使用函數式編程的狀況下更整潔。(注:reduce函數在Python3中已不是內置函數,須要從functools模塊中導入)
Filter
filter函數採用可迭代的方式,並過濾掉你在該可迭代中不須要的全部內容。
一般,filter須要一個函數和一個列表。它將函數應用於列表中的每一項,若是該函數返回True,則不執行任何操做。若是返回False,則從列表中刪除該項。
語法以下:
讓咱們看一個小例子,沒有filter咱們會寫:
使用filter,能夠這樣寫:
Python做爲一門不斷髮展與普及的語言,還在不斷更新中。在學習時,建議找一些學習夥伴一塊兒來學習和討論,效果更佳。若是想學習Python,歡迎加入Python學習交流羣(627012464),一塊兒督促,一塊兒學習。裏面有開發工具,不少乾貨和技術資料分享!
高階函數
高階函數能夠將函數做爲參數並返回函數。一個很是簡單的例子以下:
第二個返回函數的例子:
開頭我說過純函數式編程語言沒有變量。更高階的函數使這變得更容易。
Python中的全部函數都是一等公民。一等公民被定義爲具備如下一個或多個特徵:
在運行時建立
在數據結構中分配變量或元素
做爲函數的參數傳遞
做爲函數的結果返回
Python中的全部函數均可以用做高階函數。
Partial application
Partial application(也稱爲閉包)有點奇怪,但很是酷。您能夠在不提供所需的全部參數的狀況下調用函數。讓咱們在一個例子中看到這一點。咱們想要建立一個函數,它接受2個參數,一個基數和一個指數,並返回指數冪的基數,以下所示:
如今咱們想要一個專用的平方函數,使用冪函數計算出數字的平方:
這有效,但若是咱們想要一個立方體功能呢?或者求四次方的功能呢?咱們能夠繼續寫下它們嗎?好吧,你能夠。但程序員很懶的。若是你一遍又一遍地重複一樣的事情,這代表有一種更快的方法來加快速度,這將使你再也不重複。咱們能夠在這裏使用閉包。讓咱們看一個使用閉包的square函數的示例:
是否是很酷!咱們能夠只使用1個參數來調用須要2個參數的函數。
咱們還可使用一個循環來生成一個冪函數,該函數實現從立方體一直到1000的冪。
函數式編程不是pythonic
您可能已經注意到了,咱們想要在函數式編程中作的不少事情都圍繞着列表。除了reduce函數和閉包以外,您看到的全部函數都會生成列表。 Guido(Python之父)不喜歡Python中的函數式,由於Python已經有了本身生成列表的方法。
若是你在Python的交互環境下寫入」import this「,你將會獲得:
這是Python之禪。這是一首關於Pythonic意味着什麼的詩。咱們想要涉及的部分是:
There should be one — and preferably only one — obvious way to do it.(應該儘可能找到一種,最好是惟一一種明顯的解決方案)
在Python中,map和filter能夠執行與列表推導(下面討論)相同的操做。這打破了Python之禪的一個規則,所以函數式編程的這些部分不被視爲「pythonic」。
另外一個話題是Lambda。在Python中,lambda函數是一個普通函數。 Lambda是語法糖。這兩種說法是等價的。
普通函數能夠執行lambda函數能夠執行的全部操做,但它不能以相反的方式工做。 lambda函數不能完成普通函數能夠執行的全部操做。
這是一個簡短的論證,爲何函數式編程不能很好地適應整個Python生態系統。你可能已經注意到我以前提到了列表推導,咱們如今將討論它們。
列表推導
前面,我提到過你能夠用map或filter作的任何事情,你能夠用列表推導。列表推導是一種在Python中生成列表的方法。語法是:
讓咱們對列表中的每一個數字進行平方,例如:
咱們能夠看到如何將函數應用於列表中的每一項。咱們如何應用filter呢?看看前面的代碼:
咱們能夠將其轉換成一個列表推導,像這樣:
列表支持if這樣的語句。您再也不須要將一百萬個函數應用於某些東西以得到您想要的東西。事實上,若是你想嘗試生成某種列表,那麼使用列表推導看起來會更清晰,更容易。若是咱們想要將列表中每一個0如下的數字平方怎麼辦?有了lambda,map和filter你會寫:
這彷佛很長很複雜。經過列表推導,它只是:
列表推導僅適用於列表。map,filter適合任何可迭代的對象,那麼這有什麼用呢?你能夠對你遇到的任何可迭代對象使用任何推導。
其餘推導
你能夠爲任何可迭代對象建立一個推導。
可使用推導生成任何可迭代的對象。從Python 2.7開始,您甚至能夠生成字典(hashmap)。
若是它是可迭代的,則能夠生成它。讓咱們看一下最後一組的例子。
set是一個元素列表,在該列表中沒有元素重複兩次。
set中的元素沒有順序。
您可能會注意到set(集合)與dict(字典)具備相同的花括號。 Python很是聰明。根據你是否爲dict提供值,它會知道你是在寫dict推導仍是set推導。
總結
函數式編程美觀而純粹。函數式代碼能夠很乾淨,但也可能很亂。一些Python程序員不喜歡Python中的函數式編程。但我認爲,你應該在解決問題時,使用最佳工具。