1.函數(shù)式編程
1.1 高階函數(shù)
函數(shù)是Python內(nèi)建支持的一種封裝,我們通過(guò)把大段代碼拆成函數(shù)健田,通過(guò)一層一層的函數(shù)調(diào)用烛卧,就可以把復(fù)雜任務(wù)分解成簡(jiǎn)單的任務(wù),這種分解可以稱(chēng)之為面向過(guò)程的程序設(shè)計(jì)妓局。函數(shù)就是面向過(guò)程的程序設(shè)計(jì)的基本單元总放。
而函數(shù)式編程(請(qǐng)注意多了一個(gè)“式”字)——Functional Programming,雖然也可以歸結(jié)到面向過(guò)程的程序設(shè)計(jì)好爬,但其思想更接近數(shù)學(xué)計(jì)算局雄。
我們首先要搞明白計(jì)算機(jī)(Computer)和計(jì)算(Compute)的概念。
在計(jì)算機(jī)的層次上抵拘,CPU執(zhí)行的是加減乘除的指令代碼哎榴,以及各種條件判斷和跳轉(zhuǎn)指令,所以僵蛛,匯編語(yǔ)言是最貼近計(jì)算機(jī)的語(yǔ)言尚蝌。
而計(jì)算則指數(shù)學(xué)意義上的計(jì)算,越是抽象的計(jì)算充尉,離計(jì)算機(jī)硬件越遠(yuǎn)飘言。
對(duì)應(yīng)到編程語(yǔ)言,就是越低級(jí)的語(yǔ)言驼侠,越貼近計(jì)算機(jī)姿鸿,抽象程度低,執(zhí)行效率高倒源,比如C語(yǔ)言苛预;越高級(jí)的語(yǔ)言,越貼近計(jì)算笋熬,抽象程度高热某,執(zhí)行效率低,比如Lisp語(yǔ)言胳螟。
函數(shù)式編程就是一種抽象程度很高的編程范式昔馋,純粹的函數(shù)式編程語(yǔ)言編寫(xiě)的函數(shù)沒(méi)有變量,因此糖耸,任意一個(gè)函數(shù)秘遏,只要輸入是確定的,輸出就是確定的嘉竟,這種純函數(shù)我們稱(chēng)之為沒(méi)有副作用邦危。而允許使用變量的程序設(shè)計(jì)語(yǔ)言洋侨,由于函數(shù)內(nèi)部的變量狀態(tài)不確定,同樣的輸入倦蚪,可能得到不同的輸出凰兑,因此,這種函數(shù)是有副作用的审丘。
函數(shù)式編程的一個(gè)特點(diǎn)就是,允許把函數(shù)本身作為參數(shù)傳入另一個(gè)函數(shù)勾给,還允許返回一個(gè)函數(shù)滩报!
Python對(duì)函數(shù)式編程提供部分支持。由于Python允許使用變量播急,因此脓钾,Python不是純函數(shù)式編程語(yǔ)言。
1.1 高階函數(shù)
高階函數(shù)英文叫Higher-order function桩警。
1.11 變量可以指向函數(shù)
以Python內(nèi)置的求絕對(duì)值的函數(shù)abs()為例可训,調(diào)用該函數(shù)用以下代碼:
>>> abs(-10)
10
但是,如果只寫(xiě)abs
呢捶枢?
>>> abs
<built-in function abs>
可見(jiàn)握截,abs(-10)
是函數(shù)調(diào)用,而abs
是函數(shù)本身烂叔。
函數(shù)名也是變量
那么函數(shù)名是什么呢谨胞?函數(shù)名其實(shí)就是指向函數(shù)的變量!對(duì)于abs()這個(gè)函數(shù)蒜鸡,完全可以把函數(shù)名abs
看成變量胯努,它指向一個(gè)可以計(jì)算絕對(duì)值的函數(shù)!
傳入函數(shù)
既然變量可以指向函數(shù)逢防,函數(shù)的參數(shù)能接收變量叶沛,那么一個(gè)函數(shù)就可以接收另一個(gè)函數(shù)作為參數(shù),這種函數(shù)就稱(chēng)之為高階函數(shù)忘朝。
一個(gè)最簡(jiǎn)單的高階函數(shù):
def add(x, y, f):
return f(x) + f(y)
當(dāng)我們調(diào)用add(-5, 6, abs)
時(shí)灰署,參數(shù)x
,y
和f
分別接收-5辜伟,6和abs氓侧,根據(jù)函數(shù)定義,我們可以推導(dǎo)計(jì)算過(guò)程為:
x = -5
y = 6
f = abs
f(x) + f(y) ==> abs(-5) + abs(6) ==> 11
return 11
用代碼驗(yàn)證一下:
>>> add(-5, 6, abs)
11
編寫(xiě)高階函數(shù)导狡,就是讓函數(shù)的參數(shù)能夠接收別的函數(shù)约巷。
把函數(shù)作為參數(shù)傳入,這樣的函數(shù)稱(chēng)為高階函數(shù)旱捧,函數(shù)式編程就是指這種高度抽象的編程范式独郎。
1.11 map/reduce
Python內(nèi)建了map()
和reduce()
函數(shù)踩麦。
我們先看map。map()
函數(shù)接收兩個(gè)參數(shù)氓癌,一個(gè)是函數(shù)谓谦,一個(gè)是Iterable
,map
將傳入的函數(shù)依次作用到序列的每個(gè)元素贪婉,并把結(jié)果作為新的Iterator
返回反粥。
舉例說(shuō)明,比如我們有一個(gè)函數(shù)f(x)=x2
疲迂,要把這個(gè)函數(shù)作用在一個(gè)list [1, 2, 3, 4, 5, 6, 7, 8, 9]
上才顿,就可以用map()
實(shí)現(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()
傳入的第一個(gè)參數(shù)是f
,即函數(shù)對(duì)象本身尤蒿。由于結(jié)果r
是一個(gè)Iterator
郑气,Iterator
是惰性序列,因此通過(guò)list()函數(shù)讓它把整個(gè)序列都計(jì)算出來(lái)并返回一個(gè)list
腰池。
你可能會(huì)想尾组,不需要map()
函數(shù),寫(xiě)一個(gè)循環(huán)示弓,也可以計(jì)算出結(jié)果:
L = []
for n in [1, 2, 3, 4, 5, 6, 7, 8, 9]:
L.append(f(n))
print(L)
reduce
把一個(gè)函數(shù)作用在一個(gè)序列[x1, x2, x3, ...]
上讳侨,這個(gè)函數(shù)必須接收兩個(gè)參數(shù),reduce
把結(jié)果繼續(xù)和序列的下一個(gè)元素做累積計(jì)算避乏,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
比方說(shuō)對(duì)一個(gè)序列求和爷耀,就可以用reduce
實(shí)現(xiàn):
>>> from functools import reduce
>>> def add(x, y):
... return x + y
...
>>> reduce(add, [1, 3, 5, 7, 9])
25
把str
轉(zhuǎn)換為int
的函數(shù):
>>> 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
整理成一個(gè)str2int
的函數(shù)就是:
from functools import reduce
def str2int(s):
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]
return reduce(fn, map(char2num, s))
還可以用lambda
函數(shù)進(jìn)一步簡(jiǎn)化成:
from functools import reduce
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]
def str2int(s):
return reduce(lambda x, y: x * 10 + y, map(char2num, s))
也就是說(shuō),假設(shè)Python沒(méi)有提供int()
函數(shù)拍皮,你完全可以自己寫(xiě)一個(gè)把字符串轉(zhuǎn)化為整數(shù)的函數(shù)歹叮,而且只需要幾行代碼!
lambda函數(shù)的用法在后面介紹铆帽。
1.12 filter
Python內(nèi)建的filter()
函數(shù)用于過(guò)濾序列咆耿。
和map()
類(lèi)似,filter()
也接收一個(gè)函數(shù)和一個(gè)序列爹橱。和map()
不同的是萨螺,filter()
把傳入的函數(shù)依次作用于每個(gè)元素,然后根據(jù)返回值是True
還是False
決定保留還是丟棄該元素愧驱。
例如慰技,在一個(gè)list中,刪掉偶數(shù)组砚,只保留奇數(shù)吻商,可以這么寫(xiě):
def is_odd(n):
return n % 2 == 1
list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 結(jié)果: [1, 5, 9, 15]
把一個(gè)序列中的空字符串刪掉,可以這么寫(xiě):
def not_empty(s):
return s and s.strip()
list(filter(not_empty, ['A', '', 'B', None, 'C', ' ']))
# 結(jié)果: ['A', 'B', 'C']
可見(jiàn)用filter()
這個(gè)高階函數(shù)糟红,關(guān)鍵在于正確實(shí)現(xiàn)一個(gè)“篩選”函數(shù)艾帐。
注意到filter()
函數(shù)返回的是一個(gè)Iterator
乌叶,也就是一個(gè)惰性序列,所以要強(qiáng)迫filter()
完成計(jì)算結(jié)果柒爸,需要用list()
函數(shù)獲得所有結(jié)果并返回list
准浴。
練習(xí):回?cái)?shù)是指從左向右讀和從右向左讀都是一樣的數(shù),例如12321捎稚,909乐横。請(qǐng)利用filter()濾掉非回?cái)?shù):
def is_palindrome(n):
return str(n)[::1]==str(n)[::-1]
output = filter(is_palindrome, range(1,1000))
print(list(output))
1.1.3 sorted
排序也是在程序中經(jīng)常用到的算法。無(wú)論使用冒泡排序還是快速排序今野,排序的核心是比較兩個(gè)元素的大小晰奖。如果是數(shù)字,我們可以直接比較腥泥,但如果是字符串或者兩個(gè)dict呢?直接比較數(shù)學(xué)上的大小是沒(méi)有意義的啃匿,因此蛔外,比較的過(guò)程必須通過(guò)函數(shù)抽象出來(lái)。
sorted()
函數(shù)也是一個(gè)高階函數(shù)溯乒,它還可以接收一個(gè)key
函數(shù)來(lái)實(shí)現(xiàn)自定義的排序夹厌,例如按絕對(duì)值大小排序:
>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]
練習(xí)
假設(shè)我們用一組tuple表示學(xué)生名字和成績(jī):
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
·
請(qǐng)用sorted()
對(duì)上述列表分別按名字排序:
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
def by_name(t):
return t[0]
def by_score(t):
return t[1]
L2=sorted(L,key=by_name)
L3=sorted(L,key=by_score,reverse=True)
print (L2)
print (L3)