VOOZH about

URL: https://read01.com/7x3mEd.html

⇱ Python--函數式編程 - 壹讀


Sunday, Apr 12, 2026

Python--函數式編程

2016/08/30 來源:紅黑聯盟

函數式編程就是一種抽象程度很高的編程範式。這個概念有些抽象,簡單理解的話,只需要記住一點:函數式編程的一大特點是允許把函數本身作為參數傳入另一個函數,還允許返回一個函數!

Python並不是一種函數式程式語言,但是卻為函數式編程提供了部分支持。下面,我將舉一些在Python中常用的函數式編程的例子。

高階函數

所謂高階函數,是可以接收另一個函數作為參數的函數。這個聽起來有點新奇,因為一般情況下函數的參數都是數據變量。但是,其實我在之前說道字典按值排序的時候,我們給 sorted 函數的參數 key 賦值的時候,所賦的就是一個函數。這種將函數作為參數帶入函數的用法有時會為我們提供極大的方便。

1. lambda 表達式

先來看看如何構建匿名函數。一般對函數的定義都是通過 def 關鍵字加函數名的形式進行。可也有一類特殊的匿名函數,沒有名字,無目的地服務,但是卻能返回一個可調用的函數對象,在一些特殊的應用中(比如字典的按值排序)非常方便。具體用法如下:

設計一個兩數相加的加法匿名函數

lambda x, y: x + y

緊跟lambda關鍵字之後的是函數的參數,參數之後一個 「:」 號,後面是一個表達式,代表函數的返回值。lambda表達式只有一行,且代表了一個函數對象。

像上面這樣,僅僅一個lambda表達式是沒什麼用的,這就好比是一個對象,卻沒有任何引用指向它,想要應用這個函數,需要一個引用,例如

addXY = lambda x, y: x + y print(addXY(1, 2)) # >>> 3

這個例子就再清楚不過了,lambda表達式創建了一個函數對象,引用addXY指向這個對象。

true = lambda :True

2. 高階函數的應用

lambda表達式為我們展示了返回對象為函數的情況,那麼,也就可以再深一步,看看函數對象能不能被當做參數傳入函數。

看下面這個例子:

double = lambda x: x * 2 def double_add(x, y, double_fun): return double_fun(x) + double_fun(y) print(double_add(1, 2, double)) # >>> 6

先是lambda表達式創建了一個函數對象double,然後,double_add 函數將函數double當做一個參數使用。我們發現,代碼是完全沒有問題的。

當然,不止是lambda表達式,任何定義的函數,它的函數名都可以以參數(實參)的形式帶入其他函數,比如,下面的這三個函數的參數中就有別的函數名。

filter, map, 和 reduce

初步了解了什麼是高階函數,我們就來看看Python中常用的三種高階函數。

1. filter

顧名思義,過濾器。語法是這樣的:filter(func, seq) 也就是說,將一個序列和返回True or False的函數作為參數,通過函數來篩選序列,保留序列中使得函數值為True的的元素。具體用法如下

array = [1, 2, 3, 4, 5, 6] def isEven(num): return num % 2 == 0 for i in filter(isEven, array): print(i) # 依次輸出2, 4, 6

可見,這裡 filter 函數以判斷偶數的函數 isEven 的函數名,和列表array作為參數,完成了對列表中元素是偶數的篩選,篩選的過程其實是依次將序列的每一個元素帶入函數,函數會返回一個True or False,將返回True的元素作為篩選成功者。最後,filter 函數生成的是一個可疊代對象。

2. map

map 函數與 filter 類似,以函數和一個序列作為參數,將序列的每一個元素帶入函數,函數的計算結果通過一個可疊代的對象返回。

a = [1, 2, 3, 4] for i in map(lambda x: x + 1, a): print(i) # 依次輸出2, 3, 4, 5

這裡,用lambda表達式替代了顯式的函數名,當然是可以允許的。

函數要完成的計算是給序列的每個元素+1,我們發現,通過 map 函數輕鬆實現了函數對於沒個變量的「映射」。

此外,map 當中也能存在不止一個序列,對於有多個序列的 map 函數,則會並行地疊代每個序列:

a = [1, 2, 3] b = [2, 3, 4] for i in map(lambda x, y: x + y, a, b): print(i) # >>> 依次輸出3, 5, 7

可見,map是對這兩個序列並行疊代,每次都計算出一個結果。

當然,其實map 與 filter 實現的功能我們之前講的列表解析也可以完成,而且會更簡單(詳見:Python–列表解析

3. reduce

reduce這歌單詞的含義是減少、降低的意思。Python當中實現的效果也是這樣,他的語法規則如下:

reduce(func, [x1, x2, ..., xn]) = reduce(func, (func(x1, x2), [x3, ..., xn]))

有點類似於一個降維的過程,對一個序列的前兩個元素執行函數的運算(當然,reduce裡面使用的函數就一定是二元函數了),運算的結果作替代前兩個元素加入序列,這樣,整個序列就變成了n - 1長的,循環往復,直到只有一個元素時,輸出結果

我們嘗試用這種方法實現對一個列表的加和

from functools import reduce a = [1, 2, 3] print(reduce(lambda x, y: x + y, a)) # >>> 6

注意用reduce的時候應該先導入相關的模塊。

當然,對列表加和這種運算,直接用 sum 就行,是不必這麼大費周章的,但是如果要把序列[1, 2, 3, 4]變換成整數1234,reduce就可以派上用場:

from functools import reduce print(reduce(lambda x, y: x * 10 + y, [1, 2, 3, 4])) # >>> 1234

對於這種兩兩疊代的運算,reduce是十分方便的。

偏函數的應用

Python中,偏函數的作用就是幫助我們固定一些函數的參數,使得代碼的書寫更加方便。

經常會遇到這種情況,我們的程序中經常出現某個函數,且這個函數的某一個參數經常為一個固定的值,而我出於某種原因,不想或不能設置這個值為默認參數。比如,將一個二進位字符串轉換成相應的整數,應該這樣做

s = "1001" print(int(s, base=2)) # >>> 9

而我現在可能需要大量應用這個函數為了方便,可以自己定義一個新函數:

def int2(s): return int(s, base=2) s = "1001" print(int2(s)) # >>> 9

而Python提供的偏函數機制就省去了我們自己定義的這個過程:

import functools int2 = functools.partial(int, base=2) s = "1001" print(int2(s)) # >>> 9

可見,偏函數能直接建立新函數。

當然,這個新函數的參數也是可以根據情況臨時調整的

import functools int2 = functools.partial(int, base=2) s = "1001" print(int2(s, base=10)) # >>> 1001

應用偏函數時,一定需要警惕設置參數的時候,標清關鍵字,否則,會出現類似下面的錯誤:

import functools def f(a, b="result: "): return b + str(a) f1 = functools.partial(f, 2) print(f1) # >>> "result: 2"

此時的偏函數 f1 中,我們固定的參數是a,那現在這樣是沒有問題的。但是如果想要固定的參數是b,再這麼寫就會報錯了:

import functools def f(a, b="result: "): return b + str(a) f1 = functools.partial(f, "new_result: ") print(f1(1)) # wrong!

尤其是參數一多,就更容易出錯,所以,乾脆就多「長點心」,在設置偏函數的時候,標清關鍵字。

閉包

閉包的概念,一向是不好理解的。所以,我先從內嵌函數說起,循序漸進。

內嵌函數

Python中允許在函數的內部再定義別的函數。我們把這種在函數內部定義的函數稱為內嵌函數。比如下面這個例子:

def f_out: def f_in: return True return f_in print(f_out) # >>> True

在函數 f_out 內部,我們定義了函數 f_in ,函數 f_in 的作用是返回 True ,而函數 f_out 則是返回函數 f_in 的結果。整個代碼的意思是很清晰的。充分說明了函數內部是可以定義其他函數並在函數內部調用內嵌函數的。

由上一節Python–函數 可知,這個內嵌函數只能在外部函數的內部調用,如果在外部函數的函數體外調用,則一定會出錯,道理跟在函數外部引用函數內部變量是一致的。比如:

def f_out: def f_in: return True return f_in print(f_in) # wrong!

在外部作用域調用內嵌函數,出錯!

閉包

理解了內嵌函數,那就看看內嵌函數能否引用外部函數體的變量。還是上面的例子,我們略作改變:

def f_out: a = 1 def f_in: return a + 1 return f_in print(f_out) # >>> 2 print(a) # wrong!

代碼第7行正確運行,結果是2.

這說明在函數 f_out 內部調用內嵌函數 f_in 時,位於外部函數作用域的變量a成功地被內嵌函數調用了。

代碼第8行運行出錯。

這道理不再重複了,還是內部變量不能被外部引用的問題。

好了,既然現在已經知道內嵌函數能夠引用外部函數的變量。那我們再進一步:既然前面的函數式編程的例子告訴我們函數可以將函數作為返回值,那如果外部函數將內嵌函數作為返回值返回呢?

接下來,我們就來看一個非常經典的閉包的例子。此時,先不要管到底什麼是閉包,只是看下面這個例子:

def counter(begin): count = [begin] def incr: count[0] += 1 return count[0] return incr

上面的代碼中,我們定義了一個函數 counter 用作計數器。這個函數也是外部函數,在它裡面,我們定義了函數 incr 作為內嵌函數,incr 引用了外部函數的變量:列表count. 這些都跟我們之前講的例子沒什麼區別,最大的不同在於最後函數將它內部定義的內嵌函數返回。

這樣做有什麼意義呢?我們可以這樣調用:

def counter(begin): count = [begin] def incr: count[0] += 1 return count[0] return incr # 將外部函數的返回值(也就是內嵌函數)賦值給了一個外部變量 my_fun = counter(1) print(my_fun) # >>> 2 print(my_fun) # >>> 3 print(my_fun) # >>> 4

我們發現,一個奇怪的現象出現了:按說,當外部函數 counter 運行結束時,它的內部變量:列表count就應該消失才對,但是每次調用my_fun,輸出是不一樣的,那也就是說,列表count像是不曾消失一樣,始終伴隨著內嵌函數。

接下來,給出兩個概念:

1. 閉包:如果在一個內部函數中,對外部函數作用域的變量(注意不是全局作用域)進行引用,那麼這個內部函數就被稱為閉包

2. 自由變量:被閉包引用的外部變量就被稱為自由變量。

換個角度來理解這個問題:如果電腦程式被分成:代碼+數據,那麼將數據保存起來再使用時好理解的,但是將代碼保存起來,就不是那麼好理解了。而實際上,閉包就是一種將代碼(有狀態)保存起來的一種方式。

閉包也是函數,卻會攜帶一些額外的作用域(我覺得這些作用域可以理解為函數的狀態),我們使用閉包的目的就是將這些有狀態的函數封裝起來,以便以後使用。從這個角度來看,其實閉包也是數據。

所以說,想要理解閉包,就一定要將代碼同數據統一起來看,他們不是割裂的,而是「你中有我,我中有你」。理解了這一點,閉包就順理成章了。

您可能感興趣
免責聲明:本文內容來源于紅黑聯盟,文章觀點不代表壹讀立場,如若侵犯到您的權益,或涉不實謠言,敬請向我們提出檢舉
最新文章 / 服務條款 / 私隱保護 / DMCA / 聯絡我們

壹讀/READ01.COM