搜尋
首頁後端開發Python教學Python進階:高階函數的詳細說明

這篇文章講述了Python進階:高階函數的詳細說明有需要的朋友可以參考

函數式程式設計

函數是Python內建支援的一種封裝,我們透過把大段程式碼拆成函數,透過一層一層的函數調用,就可以把複雜任務分解成簡單的任務,這種分解可以稱之為面向過程的程式設計。函數就是過程導向的程式設計的基本單元。

而函數式程式設計(請注意多了一個「式」字)-Functional Programming,雖然也可以歸結到過程導向的程式設計,但其想法更接近數學計算。

我們首先要搞清楚電腦(Computer)和計算(Compute)的概念。

在電腦的層次上,CPU執行的是加減乘除的指令碼,以及各種條件判斷和跳躍指令,所以,組合語言是最貼近電腦的語言。

而計算則指數學意義上的計算,越是抽象的計算,離電腦硬體越遠。

對應到程式語言,就是越低階的語言,越貼近計算機,抽象程度低,執行效率高,例如C語言;越高階的語言,越貼近計算,抽象程度高,執行效率低,例如Lisp語言。

函數式程式設計就是一種抽象程度很高的程式設計範式,純粹的函數式程式語言所寫的函數沒有變量,因此,任意一個函數,只要輸入是確定的,輸出就是確定的,這種純函數我們稱之為沒有副作用。而允許使用變數的程式設計語言,由於函數內部的變數狀態不確定,同樣的輸入,可能得到不同的輸出,因此,這種函數是有副作用的。

函數式程式設計的一個特點是,允許把函數本身當作參數傳入另一個函數,也允許傳回一個函數!

Python對函數式程式設計提供部分支援。由於Python允許使用變量,因此,Python不是純函數式程式語言。

高階函數

高階函數英文叫Higher-order function。什麼是高階函數?我們以實際程式碼為例子,一步一步深入概念。

變數可以指向函數

以Python內建的求絕對值的函數abs()為例,呼叫函數用以下程式碼:

>>> abs(-10)10

但是,如果只寫abs呢?

>>> abs

可見,abs(-10)是函數調用,而abs是函數本身。

要得到函數呼叫結果,我們可以把結果賦值給變數:

>>> x = abs(-10)>>> x10

但是,如果把函數本身賦值給變數呢?

>>> f = abs
>>> f

#結論:函數本身也可以賦值給變量,即:變數可以指向函數。

如果一個變數指向了一個函數,那麼,可否透過該變數來呼叫這個函數?用程式碼驗證一下:

>>> f = abs>>> f(-10)10

成功!說明變數f現在已經指向了abs函數本身。直接呼叫abs()函數和呼叫變數f()完全相同。

函數名稱也是變數

那麼函數名稱是什麼呢?函數名其實就是指向函數的變數!對於abs()這個函數,完全可以把函數名abs看成變量,它指向一個可以計算絕對值的函數!

如果把abs指向其他對象,會有什麼情況發生?

>>> abs = 10
>>> abs(-10)
Traceback (most recent call last):
 File "" , line 1, in
TypeError: 'int' object is not callable

把abs指向10後,就無法透過abs(-10)呼叫該函數了!因為abs這個變數已經不指向求絕對值函數而是指向一個整數10!

當然實際程式碼絕對不能這麼寫,這裡是為了說明函數名稱也是變數。若要恢復abs函數,請重新啟動Python互動環境。

註:由於abs函數實際上是定義在import builtins模組中的,所以要讓修改abs變數的指向在其它模組也生效,要用import builtins; builtins.abs = 10。

傳入函數

既然變數可以指向函數,函數的參數能接收變量,那麼一個函數就可以接收另一個函數作為參數,這種函數就稱之為高階函數。

一個最簡單的高階函數:

def add(x, y, f):    return f(x) + f(y)

#當當我們呼叫add(-5, 6, abs)時,參數x,y和f分別接收-5,6和abs,根據函數定義,我們可以推導出計算過程為:

x = -5
y = 6
f = abs
f(x) + f(y) ==> abs(-5) + abs(6) ==> 11return 11

用程式碼驗證一下:

>>> add(-5, 6, abs)11

寫高階函數,就是讓函數的參數能夠接收別的函數。

小結

把函數當作參數傳入,這樣的函數稱為高階函數,函數式程式設計就是指這種高度抽象的程式設計範式。

map/reduce

Python內建了map()和reduce()函數。

如果你讀過Google的那篇大名鼎鼎的論文“MapReduce: Simplified Data Processing on Large Clusters”,你就能大概明白map/reduce的概念。

我們先看map。 map()函數接收兩個參數,一個是函數,一個是Iterable,map將傳入的函數依序作用到序列的每個元素,並將結果傳回作為新的Iterator。

舉例說明,例如我們有一個函數f(x)=x2,要把這個函數作用在一個list [1, 2, 3, 4, 5, 6, 7, 8, 9]上,就可以用map()實作如下:

Python進階:高階函數的詳細說明

現在,我們用Python程式碼實作如下:




現在,我們用Python程式碼實作:

>>> def f(x): ...     return x * x

...>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])>>> list(r)

[1, 4, 9, 16, 25, 36, 49, 64, 81]


map()傳入的第一個參數是f,即函數對象本身。由於結果r是一個Iterator,Iterator是惰性序列,因此透過list()函數讓它把整個序列都計算出來並傳回一個list。

你可能會想,不需要map()函數,寫一個循環,也可以計算出結果:

L = []for n in [1, 2, 3, 4, 5, 6, 7, 8, 9]:

   L.append(f(n))

print(L)


的確可以,但是,從上面的循環程式碼,能一眼看懂「把f(x)作用在list的每一個元素並把結果生成一個新的list」嗎?

所以,map()作為高階函數,事實上它把運算規則抽象化了,因此,我們不但可以計算簡單的f(x)=x2,還可以計算任意複雜的函數,比如,把這個list所有數字轉為字串:

>>> list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))[ '1', '2', '3', '4', '5', '6', '7', '8', '9']


只需要一行程式碼。

再看reduce的用法。 reduce把一個函數作用在一個序列[x1, x2, x3, ...]上,這個函數必須接收兩個參數,reduce把結果繼續和序列的下一個元素做累積計算,其效果就是:

reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

比方說對一個序列求和,就可以用reduce實作:

>>> from functools import reduce>>> def add(x, y):...     return x + y

...> ;>> reduce(add, [1, 3, 5, 7, 9])25


當然求和運算可以直接用Python內建函數sum(),沒必要動用reduce 。

但是如果要把序列[1, 3, 5, 7, 9]變換成整數13579,reduce就可以派上用場:

>>> from functools import reduce> ;>> def fn(x, y):...     return x * 10 + y

...>>> reduce(fn, [1, 3, 5, 7, 9]) 13579


這個例子本身沒多大用處,但是,如果考慮到字串str也是一個序列,對上面的例子稍加改動,配合map(),我們就可以寫出把str轉換為int的函數:

>>> from functools import reduce>>> def fn(x, y):...     return x * 10 + y

...> ;>> def char2num(s):...     return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s]###...>>> reduce(fn, map(char2num, ' 13579'))13579#########整理成一個str2int的函數就是:###

from functools import reducedef str2int(s):    def fn(x, y):        return x * 10 + y    def char2num(s):     10 + y    def char2num(s):     turn'1', '1' 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s]    return reduce( fn, map(char2num, s))

##也可以用lambda函數進一步簡化成:

from functools import reducedef char2num(s):    return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9 ': 9}[s]def str2int(s):    return reduce(lambda x, y: x * 10 + y, map(char2num, s))


也就是說,假設Python沒有提供int()函數,你完全可以自己寫一個把字串轉換為整數的函數,而且只需要幾行程式碼!

lambda函數的用法在後面介紹。

filter

Python內建的filter()函數用於過濾序列。

和map()類似,filter()也接收一個函數和一個序列。和map()不同的是,filter()把傳入的函數依序作用於每個元素,然後根據回傳值是True還是False決定保留還是丟棄該元素。

例如,在一個list中,刪除偶數,只保留奇數,可以這麼寫:

def is_odd(n):    return n % 2 == 1


# list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))# 結果: [1, 5, 9, 15]

把一個序列中的空字串刪掉,可以這麼寫:

def not_empty(s):    return s and s.strip()


list(filter(not_empty, ['A', '' , 'B', None, 'C', '  ']))# 結果: ['A', 'B', 'C']

可見用filter()這個高階函數,關鍵在於正確實作一個「篩選」函數。

注意到filter()函數回傳的是一個Iterator,也就是一個惰性序列,所以要強迫filter()完成計算結果,需要用list()函數得到所有結果並回傳list。

sorted

排序演算法

排序也是程式中常用到的演算法。無論使用冒泡排序或快速排序,排序的核心是比較兩個元素的大小。如果是數字,我們可以直接比較,但如果是字串或兩個dict呢?直接比較數學上的大小是沒有意義的,因此,比較的過程必須透過函數抽象化。

Python內建的sorted()函數就可以對list進行排序:

>>> sorted([36, 5, -12, 9, -21])[- 21, -12, 5, 9, 36]


此外,sorted()函數也是高階函數,它還可以接收一個key函數來實現自訂的排序,例如按絕對值大小排序:

>>> sorted([36, 5, -12, 9, -21], key=abs)

[5, 9, -12, -21, 36 ]

key指定的函數將作用於list的每個元素上,並根據key函數傳回的結果進行排序。比較原始的list和經過key=abs處理過的list:

list = [36, 5, -12, 9, -21]keys = [36, 5,  12, 9,  21]


然後sorted()函數依照keys排序,並依照對應關係回傳list對應的元素:

keys排序結果=> [5, 9,  12,  21, 36]

               |  | 我
#>>> sorted(['bob', 'about', 'Zoo', 'Credit'])['Credit', 'Zoo', 'about', 'bob']

預設情況下,對字串排序,是按照ASCII的大小比較的,由於'Z' 現在,我們提出排序應該忽略大小寫,依照字母序排序。要實作這個演算法,不必對現有程式碼大加改動,只要我們能用一個key函數把字串映射為忽略大小寫排序即可。忽略大小寫來比較兩個字串,其實就是先把字串都變成大寫(或者都變成小寫),再比較。


這樣,我們給sorted傳入key函數,即可實作忽略大小寫的排序:

>>> sorted(['bob', 'about', 'Zoo ', 'Credit'], key=str.lower)

['about', 'bob', 'Credit', 'Zoo']

要進行反向排序,不必改變key函數,可以傳入第三個參數reverse=True:

>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str. lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']

從上述例子可以看出,高階函數的抽象能力是非常強大的,而且,核心程式碼可以保持得非常簡潔。

小結

sorted()也是一個高階函數。用sorted()排序的關鍵在於實作一個映射函數。

傳回函數

函數當作傳回值

高階函數除了可以接受函數當參數外,還可以把函數傳回作為結果值。

我們來實作一個可變參數的求和。通常情況下,求和的函數是這樣定義的:

def calc_sum(*args):
   ax = 0    for n in args:
         = ax + n    return ax 

但是,如果不需要立刻求和,而是在後面的程式碼中,根據需要再計算怎麼辦?可以不回傳求和的結果,而是回傳求和的函數:

def lazy_sum(*args):    def sum():

       ax = 0       for n in args:
ax. ax + n        return ax    return sum

當我們呼叫lazy_sum()時,回傳的不是求和結果,而是求和函數:

>>> f = lazy_sum(1, 3, 5, 7, 9)

>>> f
.sum at 0x101c6ed90>






##。當函數f時,才真正計算求和的結果:

>>> f()25

在這個例子中,我們在函數lazy_sum中又定義了函數sum,並且,內部函數sum可以引用外部函數lazy_sum的參數和局部變量,當lazy_sum返回函數sum時,相關參數和變數都保存在傳回的函數中,這種稱為「閉包(Closure)」的程序結構擁有極大的威力。

請再注意一點,當我們呼叫lazy_sum()時,每次呼叫都會回傳一個新的函數,即使傳入相同的參數:


>>> f1 = lazy_sum(1, 3, 5, 7, 9)>>> f2 = lazy_sum(1, 3, 5, 7, 9)>>> f1==f2False







######################################################################1##### #f1()和f2()的呼叫結果互不影響。 ######閉包######注意到傳回的函數在其定義內部引用了局部變數args,所以,當一個函數傳回了一個函數後,其內部的局部變數仍被新函數引用,所以,閉包用起來簡單,實作起來可不容易。 ######另一個要注意的問題是,傳回的函數並不是立刻執行,而是直到呼叫了f()才執行。讓我們來看一個例子:######def count():###    fs = []    for i in range(1, 4):        def f():         f)    return fs######f1, f2, f3 = count()#########在上面的例子中,每次循環,都創建了一個新的函數,然後,把創建的3個函數都回傳了。 ######你可能認為呼叫f1(),f2()和f3()結果應該是1,4,9,但實際結果是:######>>> f1() 9>>> f2()9>>> f3()9#########全部都是9!原因就在於傳回的函數引用了變數i,但它並非立刻執行。等到3個函數都回傳時,它們所引用的變數i已經變成了3,因此最終結果為9。 ######返回閉包時牢記的一點是:返回函數不要引用任何循環變量,或後續會發生變化的變數。 ######如果一定要引用循環變數怎麼辦?方法是再建立一個函數,用該函數的參數綁定循環變數目前的值,無論該循環變數後續如何更改,已綁定到函數參數的值不變:######def count(): def f(j):        def g():            return j*j        return g###    fs = []    for i in range(1, 4):###        fs.append(f(i)) # f(i)立刻被執行,因此i的目前值被傳入f()    return fs##########再看看結果:######>>> f1, f2, f3 = count ()>>> f1()1>>> f2()4>>> f3()9#########缺點是程式碼較長,可利用lambda函數縮短代碼。 ######小結######一個函數可以回傳一個計算結果,也可以傳回一個函數。 ######傳回一個函數時,牢記函數並未執行,在返回函數中不要引用任何可能會變化的變數。 ######匿名函數######當我們在傳入函數時,有些時候,不需要明確地定義函數,直接傳入匿名函數會更方便。 ######在Python中,對匿名函數提供了有限支援。還是以map()函數為例,當計算f(x)=x2時,除了定義一個f(x)的函數外,還可以直接傳入匿名函數:######>>> list (map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))[1, 4, 9, 16, 25, 36, 49, 64, 81] #########透過比較可以看出,匿名函數lambda x: x * x其實就是:######def f(x):    return x * x######### #關鍵字lambda表示匿名函數,冒號前面的x表示函數參數。 ###

匿名函數有個限制,就是只能有一個表達式,不用寫return,回傳值就是該表達式的結果。

用匿名函數有個好處,因為函數沒有名字,所以不必擔心函數名稱衝突。此外,匿名函數也是函數對象,也可以把匿名函數賦值給一個變量,再利用變數來呼叫函數:

>>> f = lambda x: x * x
> ;>> f
at 0x101c6ef28>
>>> f(5)
25

一樣,也可以把匿名函數以回傳值傳回,例如:

def build(x, y):    return lambda: x * x + y * y

小結

Python對匿名函數的支援有限,只有一些簡單的情況下可以使用匿名函數。

偏函數

Python的functools模組提供了許多有用的功能,其中一個就是偏函數(Partial function)。要注意,這裡的偏函數和數學意義上的偏函數不一樣。

在介紹函數參數的時候,我們講到,透過設定參數的預設值,可以降低函數呼叫的難度。而偏函數也可以做到這一點。舉例如下:

int()函數可以把字串轉換為整數,當只傳入字串時,int()函數預設會以十進位轉換:

>>> int('12345')12345

但int()函數也提供額外的base參數,預設值為10。如果傳入base參數,就可以做N進位的轉換:

>>> int('12345', base=8)5349
>>> int(' 12345', 16)74565

假設要轉換大量的二進位字串,每次都傳入int(x, base=2)非常麻煩,於是,我們想到,可以定義一個int2( )的函數,預設把base=2傳進去:

def int2(x, base=2):    return int(x, base)

#這樣,我們轉換二進位就非常方便了:

>>> int2('1000000')64>>> int2('1010101')85

functools.partial就是幫助我們建立一個偏函數的,不需要我們自己定義int2(),可以直接用下面的程式碼建立一個新的函數int2:

>>> import functools>>> int2 = functools .partial(int, base=2)>>> int2('1000000')64>>> int2('1010101')85

所以,簡單總結functools.partial的作用就是,把一個函數的某些參數給固定住(也就是設定預設值),回傳一個新的函數,呼叫這個新函數會比較簡單。

注意到上面的新的int2函數,只是把base參數重新設定預設值為2,但也可以在函數呼叫時傳入其他值:

>> > int2('1000000', base=10)1000000

最後,建立偏函數時,實際上可以接收函數物件、*args和**kw這3個參數,當傳入:

int2 = functools.partial(int, base=2)

#其實固定了int()函數的關鍵字參數base,也就是:

int2('10010')

相當於:

kw = { 'base': 2 }int('10010', **kw)

#當傳入:

max2 = functools.partial(max, 10)

實際上會把10當作*args的一部分自動加到左邊,也就是:

max2(5, 6, 7)

相當於:

args = (10, 5, 6, 7)
max(*args )

結果為10。

小結

當函數的參數數量太多,需要簡化時,使用functools.partial可以建立一個新的函數,這個新函數可以固定住原函數的部分參數,從而在調用時更簡單。


以上是Python進階:高階函數的詳細說明的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
Python vs. C:了解關鍵差異Python vs. C:了解關鍵差異Apr 21, 2025 am 12:18 AM

Python和C 各有優勢,選擇應基於項目需求。 1)Python適合快速開發和數據處理,因其簡潔語法和動態類型。 2)C 適用於高性能和系統編程,因其靜態類型和手動內存管理。

Python vs.C:您的項目選擇哪種語言?Python vs.C:您的項目選擇哪種語言?Apr 21, 2025 am 12:17 AM

選擇Python還是C 取決於項目需求:1)如果需要快速開發、數據處理和原型設計,選擇Python;2)如果需要高性能、低延遲和接近硬件的控制,選擇C 。

達到python目標:每天2小時的力量達到python目標:每天2小時的力量Apr 20, 2025 am 12:21 AM

通過每天投入2小時的Python學習,可以有效提升編程技能。 1.學習新知識:閱讀文檔或觀看教程。 2.實踐:編寫代碼和完成練習。 3.複習:鞏固所學內容。 4.項目實踐:應用所學於實際項目中。這樣的結構化學習計劃能幫助你係統掌握Python並實現職業目標。

最大化2小時:有效的Python學習策略最大化2小時:有效的Python學習策略Apr 20, 2025 am 12:20 AM

在兩小時內高效學習Python的方法包括:1.回顧基礎知識,確保熟悉Python的安裝和基本語法;2.理解Python的核心概念,如變量、列表、函數等;3.通過使用示例掌握基本和高級用法;4.學習常見錯誤與調試技巧;5.應用性能優化與最佳實踐,如使用列表推導式和遵循PEP8風格指南。

在Python和C之間進行選擇:適合您的語言在Python和C之間進行選擇:適合您的語言Apr 20, 2025 am 12:20 AM

Python適合初學者和數據科學,C 適用於系統編程和遊戲開發。 1.Python簡潔易用,適用於數據科學和Web開發。 2.C 提供高性能和控制力,適用於遊戲開發和系統編程。選擇應基於項目需求和個人興趣。

Python與C:編程語言的比較分析Python與C:編程語言的比較分析Apr 20, 2025 am 12:14 AM

Python更適合數據科學和快速開發,C 更適合高性能和系統編程。 1.Python語法簡潔,易於學習,適用於數據處理和科學計算。 2.C 語法複雜,但性能優越,常用於遊戲開發和系統編程。

每天2小時:Python學習的潛力每天2小時:Python學習的潛力Apr 20, 2025 am 12:14 AM

每天投入兩小時學習Python是可行的。 1.學習新知識:用一小時學習新概念,如列表和字典。 2.實踐和練習:用一小時進行編程練習,如編寫小程序。通過合理規劃和堅持不懈,你可以在短時間內掌握Python的核心概念。

Python與C:學習曲線和易用性Python與C:學習曲線和易用性Apr 19, 2025 am 12:20 AM

Python更易學且易用,C 則更強大但複雜。 1.Python語法簡潔,適合初學者,動態類型和自動內存管理使其易用,但可能導致運行時錯誤。 2.C 提供低級控制和高級特性,適合高性能應用,但學習門檻高,需手動管理內存和類型安全。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

將Eclipse與SAP NetWeaver應用伺服器整合。

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)