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

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

Mar 09, 2017 am 11:03 AM
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

熱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

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

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

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

PHP和Python:解釋了不同的範例 PHP和Python:解釋了不同的範例 Apr 18, 2025 am 12:26 AM

PHP主要是過程式編程,但也支持面向對象編程(OOP);Python支持多種範式,包括OOP、函數式和過程式編程。 PHP適合web開發,Python適用於多種應用,如數據分析和機器學習。

在PHP和Python之間進行選擇:指南 在PHP和Python之間進行選擇:指南 Apr 18, 2025 am 12:24 AM

PHP適合網頁開發和快速原型開發,Python適用於數據科學和機器學習。 1.PHP用於動態網頁開發,語法簡單,適合快速開發。 2.Python語法簡潔,適用於多領域,庫生態系統強大。

Python vs. JavaScript:學習曲線和易用性 Python vs. JavaScript:學習曲線和易用性 Apr 16, 2025 am 12:12 AM

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

vs code 可以在 Windows 8 中運行嗎 vs code 可以在 Windows 8 中運行嗎 Apr 15, 2025 pm 07:24 PM

VS Code可以在Windows 8上運行,但體驗可能不佳。首先確保系統已更新到最新補丁,然後下載與系統架構匹配的VS Code安裝包,按照提示安裝。安裝後,注意某些擴展程序可能與Windows 8不兼容,需要尋找替代擴展或在虛擬機中使用更新的Windows系統。安裝必要的擴展,檢查是否正常工作。儘管VS Code在Windows 8上可行,但建議升級到更新的Windows系統以獲得更好的開發體驗和安全保障。

PHP和Python:深入了解他們的歷史 PHP和Python:深入了解他們的歷史 Apr 18, 2025 am 12:25 AM

PHP起源於1994年,由RasmusLerdorf開發,最初用於跟踪網站訪問者,逐漸演變為服務器端腳本語言,廣泛應用於網頁開發。 Python由GuidovanRossum於1980年代末開發,1991年首次發布,強調代碼可讀性和簡潔性,適用於科學計算、數據分析等領域。

visual studio code 可以用於 python 嗎 visual studio code 可以用於 python 嗎 Apr 15, 2025 pm 08:18 PM

VS Code 可用於編寫 Python,並提供許多功能,使其成為開發 Python 應用程序的理想工具。它允許用戶:安裝 Python 擴展,以獲得代碼補全、語法高亮和調試等功能。使用調試器逐步跟踪代碼,查找和修復錯誤。集成 Git,進行版本控制。使用代碼格式化工具,保持代碼一致性。使用 Linting 工具,提前發現潛在問題。

vscode怎麼在終端運行程序 vscode怎麼在終端運行程序 Apr 15, 2025 pm 06:42 PM

在 VS Code 中,可以通過以下步驟在終端運行程序:準備代碼和打開集成終端確保代碼目錄與終端工作目錄一致根據編程語言選擇運行命令(如 Python 的 python your_file_name.py)檢查是否成功運行並解決錯誤利用調試器提升調試效率

vscode 擴展是否是惡意的 vscode 擴展是否是惡意的 Apr 15, 2025 pm 07:57 PM

VS Code 擴展存在惡意風險,例如隱藏惡意代碼、利用漏洞、偽裝成合法擴展。識別惡意擴展的方法包括:檢查發布者、閱讀評論、檢查代碼、謹慎安裝。安全措施還包括:安全意識、良好習慣、定期更新和殺毒軟件。

See all articles