
Python函數式編程,從入門到?放棄?
很早以前就聽說過了函數式編程,印象中是一種很晦澀難懂的編程模式,但卻一直沒有去進行了解。
恰好這周組內的周會輪到我主持,一時也沒想到要分享什么。靈光一閃,就選定函數式編程這個主題吧,反正組里的同事都沒有學過,只需要講解入門方面的知識就好,也正好可以借這個機會逼迫自己去學習下這種新的編程方式。
經過初步了解,發現支持函數式編程的語言挺多的,除了像Lisp、Scheme、Haskell、Erlang這樣專用的函數式編程語言,我們常用的好多通用型編程語言(如Java、Python、Ruby、Javascript等)都支持函數式編程模式??紤]了下實際情況,最終還是選擇Python作為函數式編程的入門語言,因為組內同事都熟悉Python,以此作為切入點不會產生太大困難。
經過查詢資料和初步學習,對函數式編程有了些概念,經過整理,便形成了分享PPT。
以下便是這次分享的內容。
目標
通常,我們在新學習一門技術或者編程語言的時候,通常都會先從相關概念和特性入手。對于新接觸函數式編程的人來說,可能會想知道如下幾點:
什么是函數式編程?
函數式編程的特點?
函數式編程的用途?
函數式編程相比于命令式編程和面向對象編程的優缺點?
但是我這次分享卻沒有按照這個思路,因為我感覺在一開始就向聽眾灌輸太多概念性的東西,反倒會讓聽眾感到迷糊。因為經過查詢資料發現,對于什么是函數化編程,很難能有一個協調一致的定義。而且由于我也是新接觸,自身的理解可能會存在較大的偏差。
因此,我決定分享內容盡量從大家熟悉的命令式編程切入,通過大量實例來向聽眾展現函數式編程思維方式的不同之處。在這之后,再回過頭看這幾個問題,相信聽眾應該都會有更深刻的理解。
考慮到實際情況,本次分享希望能達成的目標是:
了解函數式編程與命令式編程的主要區別
掌握Python語言函數式編程的基本函數和算子
會將簡單的命令式編程語句轉換為函數式編程
命令式編程 & 函數式編程
首先從大家熟悉的命令式編程開始,我們先回顧下平時在寫代碼時主要的情景。
其實,不管我們的業務代碼有多復雜,都離不開以下幾類操作:
函數定義:def
條件控制:if, elif, else
循環控制:for, break, continue, while
當然,這只是部分操作類型,除此之外還應該有類和模塊、異常處理等等。但考慮到是入門,我們就先只關注上面這三種最常見的操作。
對應地,函數式編程也有自己的關鍵字。在Python語言中,用于函數式編程的主要由3個基本函數和1個算子。
基本函數:map()、reduce()、filter()
算子(operator):lambda
令人驚訝的是,僅僅采用這幾個函數和算子就基本上可以實現任意Python程序。
當然,能實現是一回事兒,實際編碼時是否這么寫又是另外一回事兒。估計要真只采用這幾個基本單元來寫所有代碼的話,不管是在表達上還是在閱讀上應該都挺別扭的。不過,嘗試采用這幾個基本單元來替代上述的函數定義、條件控制、循環控制等操作,對理解函數式編程如何通過函數和遞歸表達流程控制應該會很有幫助。
在開始嘗試將命令式編程轉換為函數式編程之前,我們還是需要先熟悉下這幾個基本單元。
Python函數式編程的基本單元
lambda
lambda這個關鍵詞在很多語言中都存在。簡單地說,它可以實現函數創建的功能。
如下便是lambda的兩種使用方式。
func1=lambda: <expression()>
func2=lambdax: <expression(x)>
func3=lambdax,y: <expression(x,y)>
在第一條語句中,采用lambda創建了一個無參的函數func1。這和下面采用def創建函數的效果是相同的。
def func1():
<expression()>
在第二條和第三條語句中,分別采用lambda創建了需要傳入1個參數的函數func2,以及傳入2個參數的函數func3。這和下面采用def創建函數的效果是相同的。
def func2(x):
<expression(x)>
def func3(x,y):
<expression(x,y)>
需要注意的是,調用func1的時候,雖然不需要傳入參數,但是必須要帶有括號(),否則返回的只是函數的定義,而非函數執行的結果。這個和在ruby中調用無參函數時有所不同,希望ruby程序員引起注意。
>>>func=lambda:123
>>>func
<function<lambda>at0x100f4e1b8>
>>>func()
123
另外,雖然在上面例子中都將lambda創建的函數賦值給了一個函數名,但這并不是必須的。從下面的例子中大家可以看到,很多時候我們都是直接調用lambda創建的函數,而并沒有命名一個函數,這也是我們常聽說的匿名函數的由來。
map()
map()函數的常見調用形式如下所示:
map(func, iterable)
map()需要兩個必填參數,第一個參數是一個函數名,第二個參數是一個可迭代的對象,如列表、元組等。
map()實現的功能很簡單,就是將第二個參數(iterable)中的每一個元素分別傳給第一個參數(func),依次執行函數得到結果,并將結果組成一個新的list對象后進行返回。返回結果永遠都是一個list。
簡單示例如下:
>>>double_func=lambdas:s *2
>>>map(double_func,[1,2,3,4,5])
[2,4,6,8,10]
除了傳入一個可迭代對象這種常見的模式外,map()還支持傳入多個可迭代對象。
map(func, iterable1, iterable2)
在傳入多個可迭代對象的情況下,map()會依次從所有可迭代對象中依次取一個元素,組成一個元組列表,然后將元組依次傳給func;若可迭代對象的長度不一致,則會以None進行補上。
通過以下示例應該就比較容易理解。
>>>plus=lambdax,y:(xor0)+(yor0)
>>>map(plus,[1,2,3],[4,5,6])
[5,7,9]
>>>map(plus,[1,2,3,4],[4,5,6])
[5,7,9,4]
>>>map(plus,[1,2,3],[4,5,6,7])
[5,7,9,7]
在上面的例子中,之所以采用x or 0的形式,是為了防止None + int出現異常。
需要注意的是,可迭代對象的個數應該與func的參數個數一致,否則就會出現異常,因為傳參個數與函數參數個數不一致了,這個應該比較好理解。
>>>plus=lambdax,y:x+y
>>>map(plus,[1,2,3])
Traceback(most recent calllast):
File"<stdin>",line1,in<module>
TypeError: <lambda>()takesexactly2arguments(1given)
另外,map()還存在一種特殊情況,就是func為None。這個時候,map()仍然是從所有可迭代對象中依次取一個元素,組成一個元組列表,然后將這個元組列表作為結果進行返回。
>>>map(None,[1,2,3,4])
[1,2,3,4]
>>>map(None,[1,2,3,4],[5,6,7,8])
[(1,5),(2,6),(3,7),(4,8)]
>>>map(None,[1,2,3,4],[5,6,7])
[(1,5),(2,6),(3,7),(4,None)]
>>>map(None,[1,2,3,4],[6,7,8,9],[11,12])
[(1,6,11),(2,7,12),(3,8,None),(4,9,None)]
reduce()
reduce()函數的調用形式如下所示:
reduce(func, iterable[, initializer])
reduce()函數的功能是對可迭代對象(iterable)中的元素從左到右進行累計運算,最終得到一個數值。第三個參數initializer是初始數值,可以空置,空置為None時就從可迭代對象(iterable)的第二個元素開始,并將第一個元素作為之前的結果。
文字描述可能不大清楚,看下reduce()的源碼應該就比較清晰了。
def reduce(function,iterable,initializer=None):
it=iter(iterable)
ifinitializerisNone:
try:
initializer=next(it)
exceptStopIteration:
raise TypeError('reduce() of empty sequence with no initial value')
accum_value=initializer
forxinit:
accum_value=function(accum_value,x)
returnaccum_value
再加上如下示例,對reduce()的功能應該就能掌握了。
>>>plus=lambdax,y:x+y
>>>reduce(plus,[1,2,3,4,5])
15
>>>reduce(plus,[1,2,3,4,5],10)
25
filter()
filter()函數的調用形式如下:
filter(func, iterable)
filter()有且僅有兩個參數,第一個參數是一個函數名,第二個參數是一個可迭代的對象,如列表、元組等。
filter()函數的調用形式與map()比較相近,都是將第二個參數(iterable)中的每一個元素分別傳給第一個參數(func),依次執行函數得到結果;差異在于,filter()會判斷每次執行結果的bool值,并只將bool值為true的篩選出來,組成一個新的列表并進行返回。
>>>mode2=lambdax:x%2
>>>filter(mode2,[1,2,3,4,5,6,7,8,9,10])
[1,3,5,7,9]
以上便是Python函數式編程基本單元的核心內容。
接下來,我們就開始嘗試采用新學習到的基本單元對命令式編程中的條件控制和循環控制進行轉換。
替換條件控制語句
在對條件控制進行替換之前,我們先來回顧下Python中對布爾表達式求值時進行的“短路”處理。
什么叫“短路”處理?簡單地講,就是如下兩點:
在f(x) and g(y)中,當f(x)為false時,不會再執行g(y),直接返回false
在f(x) or g(y)中,當f(x)為true時,不會再執行g(y),直接返回true
結論是顯然易現的,就不再過多解釋。
那么,對應到條件控制語句,我們不難理解,如下條件控制語句和表達式是等價的。
# flow control statement
if<cond1>: func1()
elif<cond2>:func2()
else: func3()
# Equivalent "short circuit" expression
(<cond1>andfunc1())or(<cond2>andfunc2())or(func3())
通過這個等價替換,我們就去除掉了if/elif/else關鍵詞,將條件控制語句轉換為一個表達式。那這個表達式和函數式編程有什么關系呢?
這時我們回顧上面講過的lambda,會發現lambda算子返回的就是一個表達式。
基于這一點,我們就可以采用lambda創建如下函數。
>>>pr=lambdas:s
>>>print_num=lambdax:(x==1andpr("one"))
.... or(x==2andpr("two"))
.... or(pr("other"))
>>>print_num(1)
'one'
>>>print_num(2)
'two'
>>>print_num(3)
'other'
通過函數調用的結果可以看到,以上函數實現的功能與之前的條件控制語句實現的功能完全相同。
到這里,我們就實現了命令式條件控制語句向函數式語句的轉換。并且這個轉換的方法是通用的,所有條件控制語句都可以采用這種方式轉換為函數式語句。
替換循環控制語句
接下來我們再看循環控制語句的轉換。在Python中,循環控制是通過for和while這兩種方式實現的。
替換for循環
for循環語句的替換十分簡單,采用map()函數就能輕松實現。這主要是因為for語句和map()原理相同,都是對可迭代對象里面的每一個元素進行操作,因此轉換過程比較自然。
# statement-based for loop
foreinlst: func(e)
# Equivalent map()-based loop
map(func,lst)
>>>square=lambdax:x *x
>>>forxin[1,2,3,4,5]:square(x)
...
1
4
9
16
25
>>>map(square,[1,2,3,4,5])
[1,4,9,16,25]
替換while循環
while循環語句的替換相比而言就復雜了許多。
下面分別是while循環語句及其對應的函數式風格的代碼。
# statement-based while loop
while<condition>:
<pre-suite>
if<break_condition>:
break
else:
<suite>
# Equivalent FP-style recursive while loop
def while_block():
<pre-suite>
if<break_condition>:
return1
else:
<suite>
return0
while_FP=lambda: <condition>and(while_block()orwhile_FP())
while_FP()
這里的難點在于,函數式while_FP循環采用了遞歸的概念。當為true時,進入循環體,執行while_block();若為true時,返回1,while_FP()調用結束;若為false時,返回0,會繼續執行or右側的while_FP(),從而實現遞歸調用;若始終為false,則會持續遞歸調用while_FP(),這就實現了while語句中同樣的功能。
為了對函數式的while循環有更深刻的理解,可以再看下如下示例。這個例子是在網上找的,實現的是echo功能:輸入任意非”quit”字符時,打印輸入的字符;輸入”quit”字符時,退出程序。
? PythonFP pythonpyecho.py
IMP--1
1
IMP--2
2
IMP--abc
abc
IMP--1+1
1+1
IMP--quit
quit
? PythonFP
如下便是分別采用過程式和函數式語句實現的”echo”功能。
# imperative version of "echo()"
def echo_IMP():
while1:
x=raw_input("IMP -- ")
printx
ifx=='quit':
break
echo_IMP()
def monadic_print(x):
printx
returnx
# FP version of "echo()"
echo_FP=lambda:monadic_print(raw_input("FP -- "))=='quit'orecho_FP()
echo_FP()
更多示例
到此為止,我們對函數式編程總算有了點認識,到達之前設定的目標應該是沒有問題了,看來函數式編程也并沒有想象中的那么難懂。
然而,這都只是函數式編程的皮毛而已,不信?再看下如下示例。
這個示例也是在網上找的,實現的是兩個列表笛卡爾積的篩選功能,找出笛卡爾積元組集合中兩個元素之積大于25的所有元組。
bigmuls=lambdaxs,ys:filter(lambda(x,y):x*y>25,combine(xs,ys))
combine=lambdaxs,ys:map(None,xs*len(ys),dupelms(ys,len(xs)))
dupelms=lambdalst,n:reduce(lambdas,t:s+t,map(lambdal,n=n:[l]*n,lst))
print bigmuls([1,2,3,4],[10,15,3,22])
[(3,10),(4,10),(2,15),(3,15),(4,15),(2,22),(3,22),(4,22)]
雖然這個例子中lambda/map/reduce/filter都是我們已經比較熟悉了的基本單元,但是經過組合后,理解起來還是會比較吃力。
總結
看到這里,有的同學就開玩笑說我這標題名稱非常貼切,《Python的函數式編程–從入門到?放棄?》,因為以后在工作中應該也不會再嘗試使用函數式編程了,^_^。
不過,我還是覺得函數式編程挺有意思的,更高級的特性后面值得再繼續學習。即使代碼不用寫成pure函數式風格,但在某些時候局部使用lambda/map/reduce/filter也能大大簡化代碼,也是一個不錯的選擇。數據分析師培訓
另外,通過此次分享,再次切身體會到了教授是最好的學習方式,只有當你真正能將一個概念講解清楚的時候,你才算是掌握了這個概念。
數據分析咨詢請掃描二維碼
若不方便掃碼,搜微信號:CDAshujufenxi
解碼數據基因:從數字敏感度到邏輯思維 每當看到超市貨架上商品的排列變化,你是否會聯想到背后的銷售數據波動?三年前在零售行 ...
2025-05-23在本文中,我們將探討 AI 為何能夠加速數據分析、如何在每個步驟中實現數據分析自動化以及使用哪些工具。 數據分析中的AI是什么 ...
2025-05-20當數據遇見人生:我的第一個分析項目 記得三年前接手第一個數據分析項目時,我面對Excel里密密麻麻的銷售數據手足無措。那些跳動 ...
2025-05-20在數字化運營的時代,企業每天都在產生海量數據:用戶點擊行為、商品銷售記錄、廣告投放反饋…… 這些數據就像散落的拼圖,而相 ...
2025-05-19在當今數字化營銷時代,小紅書作為國內領先的社交電商平臺,其銷售數據蘊含著巨大的商業價值。通過對小紅書銷售數據的深入分析, ...
2025-05-16Excel作為最常用的數據分析工具,有沒有什么工具可以幫助我們快速地使用excel表格,只要輕松幾步甚至輸入幾項指令就能搞定呢? ...
2025-05-15數據,如同無形的燃料,驅動著現代社會的運轉。從全球互聯網用戶每天產生的2.5億TB數據,到制造業的傳感器、金融交易 ...
2025-05-15大數據是什么_數據分析師培訓 其實,現在的大數據指的并不僅僅是海量數據,更準確而言是對大數據分析的方法。傳統的數 ...
2025-05-14CDA持證人簡介: 萬木,CDA L1持證人,某電商中廠BI工程師 ,5年數據經驗1年BI內訓師,高級數據分析師,擁有豐富的行業經驗。 ...
2025-05-13CDA持證人簡介: 王明月 ,CDA 數據分析師二級持證人,2年數據產品工作經驗,管理學博士在讀。 學習入口:https://edu.cda.cn/g ...
2025-05-12CDA持證人簡介: 楊貞璽 ,CDA一級持證人,鄭州大學情報學碩士研究生,某上市公司數據分析師。 學習入口:https://edu.cda.cn/g ...
2025-05-09CDA持證人簡介 程靖 CDA會員大咖,暢銷書《小白學產品》作者,13年頂級互聯網公司產品經理相關經驗,曾在百度、美團、阿里等 ...
2025-05-07相信很多做數據分析的小伙伴,都接到過一些高階的數據分析需求,實現的過程需要用到一些數據獲取,數據清洗轉換,建模方法等,這 ...
2025-05-06以下的文章內容來源于劉靜老師的專欄,如果您想閱讀專欄《10大業務分析模型突破業務瓶頸》,點擊下方鏈接 https://edu.cda.cn/g ...
2025-04-30CDA持證人簡介: 邱立峰 CDA 數據分析師二級持證人,數字化轉型專家,數據治理專家,高級數據分析師,擁有豐富的行業經驗。 ...
2025-04-29CDA持證人簡介: 程靖 CDA會員大咖,暢銷書《小白學產品》作者,13年頂級互聯網公司產品經理相關經驗,曾在百度,美團,阿里等 ...
2025-04-28CDA持證人簡介: 居瑜 ,CDA一級持證人國企財務經理,13年財務管理運營經驗,在數據分析就業和實踐經驗方面有著豐富的積累和經 ...
2025-04-27數據分析在當今信息時代發揮著重要作用。單因素方差分析(One-Way ANOVA)是一種關鍵的統計方法,用于比較三個或更多獨立樣本組 ...
2025-04-25CDA持證人簡介: 居瑜 ,CDA一級持證人國企財務經理,13年財務管理運營經驗,在數據分析就業和實踐經驗方面有著豐富的積累和經 ...
2025-04-25在當今數字化時代,數據分析師的重要性與日俱增。但許多人在踏上這條職業道路時,往往充滿疑惑: 如何成為一名數據分析師?成為 ...
2025-04-24