高階函數(shù)英文叫Higher-order function副渴。什么是高階函數(shù)奈附?舉例說明。
變量可以指向函數(shù)
以Python內(nèi)置的求絕對值的函數(shù)abs()
為例煮剧,調(diào)用該函數(shù)用以下代碼:
>>> abs(-10)
10
但是斥滤,如果只寫abs
呢?
>>> abs
<built-in function abs>
可見勉盅,abs(-10)
是函數(shù)調(diào)用佑颇,而abs
是函數(shù)本身。要獲得函數(shù)調(diào)用的結(jié)果草娜,我們可以把結(jié)果賦值給變量:
>>> x = abs(-10)
>>> x
10
但是挑胸,如果把函數(shù)本身賦值給變量呢?
>>> f = abs
>>> f
<built-in function abs>
結(jié)論:函數(shù)本身也可以賦值給變量宰闰,即:變量可以指向函數(shù)茬贵。
如果一個變量指向了一個函數(shù),那么就可以通過該變量來調(diào)用這個函數(shù):
>>> f = abs
>>> f(-10)
10
函數(shù)名也是變量
函數(shù)名就是指向函數(shù)的變量移袍!對于abs()
這個函數(shù)解藻,完全可以把函數(shù)名abs
看成變量,他只想一個可以計算絕對值的函數(shù)咐容。
如果把abs
指向其他對象舆逃,會發(fā)生什么?
>>> abs = 10
>>> abs(-10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
把abs
指向10
后戳粒,就無法通過abs(-10)
調(diào)用該函數(shù)了路狮!因為abs
變量已經(jīng)不指向求絕對值函數(shù)而是指向一個整數(shù)10
。當然實際代碼絕對不能這么些蔚约,這里是為了說明函數(shù)名也是變量奄妨。要恢復(fù)abs
函數(shù),請重啟交互環(huán)境苹祟。
注:由于abs
函數(shù)實際上是定義在import builtins
模塊中的砸抛,所以要讓修改abs
變量的指向在其他模塊也生效评雌,要用import builtins; builtins.abs = 10
。
傳入函數(shù)
既然便利那個可以指向函數(shù)直焙,函數(shù)的參數(shù)就能接收變量景东,那么一個函數(shù)就可以接受另一個函數(shù)作為參數(shù),這種函數(shù)就稱之為高階函數(shù)奔誓。
一個最簡單的高階函數(shù):
>>> def add(x, y, f):
... return f(x) + f(y)
當我們調(diào)用add(-5, 6, abs)
時斤吐,參數(shù)x
, y
和'f'分別接收-5
, 6
和abs
,根據(jù)函數(shù)定義厨喂,我們可以推導(dǎo)計算過程為:
x = -5
y = 6
f = abs
f(x) + f(y) == > abs(-5) + abs(6) ==> 11
return 11
小結(jié)
把函數(shù)作為參數(shù)傳入和措,這樣的函數(shù)稱為高階函數(shù),函數(shù)式編程就是指這種高度抽象的編程范式蜕煌。
1.map/reduce
Python中內(nèi)建了map()
和reduce()
函數(shù)派阱。
map()
函數(shù)接收兩個參數(shù),一個是函數(shù)斜纪,一個是Iterable
贫母,map
將傳入的函數(shù)一次作用到序列的每個元素,并把結(jié)果作為新的Iterator
返回傀广。
比如颁独,我們有一個函數(shù)f(x)=x2彩届,要把這個函數(shù)作用在一個list[1, 2, 3, 4, 5, 6, 7, 8, 9]上伪冰,就可以用map()
實現(xiàn):
>>> 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()
傳入的第一個參數(shù)是f
,即函數(shù)對象本身樟蠕。由于結(jié)果r
是一個Iterator
, Iterator
是惰性序列贮聂,因此通過list()
函數(shù)讓他把整個序列都計算出來并返回一個list。
你可能會想寨辩,我寫一個循環(huán)吓懈,也可以計算結(jié)果
L = []
for n in [1, 2, 3, 4, 5, 6, 7, 8, 9]:
L.append(f(n))
print(L)
的確可以,但是從上面的循環(huán)代碼靡狞,并不能一眼看明白“把f(x)作用在list的每一個元素并把結(jié)果生成一個新的list”耻警。所以,map()
作為高階函數(shù)甸怕,事實上他把運算規(guī)則抽象了甘穿,因此,我們不但可以計算簡單的f(x)=x2梢杭,還可以計算任意復(fù)雜的函數(shù)温兼,比如,把這個list所有數(shù)字轉(zhuǎn)為字符串:
>>> list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
['1', '2', '3', '4', '5', '6', '7', '8', '9']
reduce
把一個函數(shù)作用在一個序列[x1, x2, x3, ...]
上武契,這個函數(shù)必須接收兩個參數(shù)募判,reduce
把結(jié)果繼續(xù)和序列的下一個元素做累積計算荡含,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
比如說對一個序列求和,就可以用reduce實現(xiàn):
>>> from functools import reduce
>>> def add(x, y):
... return x + y
...
>>> reduce(add, [1, 3, 5, 7, 9])
25
當然求和運算可以直接用Python內(nèi)置函數(shù)sum()
届垫,沒必要用reduce
释液。但是如果要把序列[1, 3, 5, 7, 9]
變成整數(shù)13579
,reduce
就可以派上用場:
>>> from functools import reduce
>>> def add(x, y):
... return x * 10 + y
...
>>> reduce(add, [1, 3, 5, 7, 9])
13579
這個例子本身沒多大用處装处,但是均澳,如果考慮到字符串str
也是一個序列,對上面的例子稍加改動符衔,配合map()
找前,我們就可以寫出把str
轉(zhuǎn)換為int
的函數(shù):
>>> from functools import reduce
>>> def fn(x, y):
... return x * 10 + y
...
>>> def char2num(s):
... digits = {'0' : 0,'1' : 1,'2' : 2,'3' : 3,'4' : 4,'5' : 5,'6' : 6,'7' : 7,'8' : 8,'9' : 9}
... return digits[s]
...
>>> reduce(fn, map(char2num, '13579'))
13579
整理成一個str2int
的函數(shù)就是:
from functools import reduce
DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
def str2int(s):
def fn(x, y):
return x * 10 + y
def char2num(s):
return DIGITS[s]
return reduce(fn, map(char2num, s))
進一步簡化
from functools import reduce
DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
def char2num(s):
return DIGITS[s]
def str2int(s):
return reduce(lambda x, y: x * 10 + y, map(char2num, s))
filter
Python內(nèi)建的filter()
函數(shù)用于過濾序列。
和map()
類似判族,filter()
也接收一個函數(shù)和一個序列躺盛。和map()
不同的是,filter()
把傳入的函數(shù)一次作用于每個元素形帮,然后根據(jù)返回值是True
還是False
決定保留還是丟棄該元素槽惫。
例如,在一個list中辩撑,刪掉偶數(shù)界斜,只保留奇數(shù),可以這么寫:
>>> 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()
這個高階函數(shù)各薇,關(guān)鍵在于正確實現(xiàn)一個“篩選”函數(shù)。注意到filter()
函數(shù)返回的是一個Iterator
君躺,也就是一個惰性序列峭判,所以要強迫fiflter()
完成計算結(jié)果,需要用list()
函數(shù)獲得所有結(jié)果并返回list棕叫。
sorted
排序算法
排序也是在程序中經(jīng)常用到的算法林螃。無論使用冒泡排序還是快速排序,排序的核心是比較兩個元素的大小俺泣。如果是數(shù)字疗认,我們可以直接比較,但是如果是字符串或者是兩個dict呢伏钠?直接比較數(shù)學上的大小是沒有意義的横漏,因此,比較的過程必須通過函數(shù)抽象出來贝润。
Python內(nèi)置的sorted()
函數(shù)就可以對list進行排序:
>>>sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]
此外绊茧,sorted()
函數(shù)也是一個高階函數(shù),他還可以接收一個key
函數(shù)來實現(xiàn)自定義的排序打掘,例如按照絕對值大小排序:
sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]
key指定的函數(shù)將作用于list的每一個元素上华畏,并根據(jù)key函數(shù)返回的結(jié)果進行排序鹏秋。對比原始的list和經(jīng)過key=abs
處理過的list:
list = [36, 5, -12, 9, -21]
keys = [36, 5, 12, 9, 21]
我們再看一個字符串排序的例子:
>>>sorted(['bob', 'about', 'Zoo', 'Credit'])
['Credit', 'Zoo', 'about', 'bob']
默認情況下,對字符串排序亡笑,是按照ASCII的大小比較的侣夷,由于'Z' < 'a'
,結(jié)果仑乌,大寫字母Z
會排在小寫字母a
前面