函數(shù)式編程
函數(shù)式編程就是一種給抽象程度很高的編程范式,純粹的函數(shù)式編程語(yǔ)言編寫(xiě)的函數(shù)沒(méi)有變量腊敲,因此击喂,任意一個(gè) 函數(shù),只要輸入是正確的碰辅,輸出就是正確的懂昂。而允許使用變量的程序設(shè)計(jì)語(yǔ)言,由于函數(shù)內(nèi)部的變量狀態(tài)不確定没宾,同樣的輸入凌彬,可能得到不同的輸出。
函數(shù)式編程的一個(gè)特點(diǎn)就是循衰,允許把函數(shù)本身作為參數(shù)傳入另一個(gè)函數(shù)铲敛,還允許返回一個(gè)函數(shù)。
高階函數(shù)
變量可以指向函數(shù)
即:
>>> abs(-10)
10
>>> abs
<built-in function abs>
>>> x = abs(-10)
>>> x
10
>>> f = abs
>>> f
<built-in function abs>
#即:
>>> f(-10)
10
函數(shù)名也是變量
即:
>>> abs = 10
>>> abs(-10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
把abs
指向10
后会钝,就無(wú)法通過(guò)abs(-10)
調(diào)用該函數(shù)了伐蒋,因?yàn)?code>abs這個(gè)變量已經(jīng)不指向求絕對(duì)值函數(shù)而是指向一個(gè)整數(shù)10
,要恢復(fù)abs
函數(shù)迁酸,請(qǐng)重啟Python交互環(huán)境
傳入函數(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)
map/reduce
python內(nèi)建了map()
和reduce()
函數(shù)
map()
函數(shù)接收臉啊哥哥參數(shù),一個(gè)是函數(shù)串远,一個(gè)是Iterable
宏多,map
將傳入的函數(shù)一次作用到序列的每個(gè)元素,并把結(jié)果作廢新的Iterator
返回抑淫。即:
def f(x):
return x ** 2
r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
print(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
map()
作為高階函數(shù),事實(shí)上它把運(yùn)算規(guī)則抽象化,作用在Iterable
的每一個(gè)元素并生成一個(gè)新的Iterator
reduce()``函數(shù)函喉,把一個(gè)函數(shù)作用在一個(gè)序列上避归,這個(gè)函數(shù)必須接收兩個(gè)參數(shù),
reduce`把結(jié)果繼續(xù)和序列的洗一個(gè)元素做累積計(jì)算管呵,其效果是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2,), x3), x4)
#對(duì)一個(gè)序列求和
>>> from functools import reduce
>>> def add(x, y)
return x + y
>>> reduce(add, [1, 3, 5, 7, 9])
25
filter
filter()
函數(shù)用于過(guò)濾序列和map()
相似梳毙,filter()
也接收一個(gè)函數(shù)和一個(gè)序列。和map()
不同的是捐下,filter()
把純?nèi)绲暮瘮?shù)依次作用于每個(gè)元素账锹,然后根據(jù)返回值的True
和False
決定保留還是丟棄該元素,例:保留奇數(shù)
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]
可看出坷襟,上述舉例中is_odd()
可以看作是一個(gè)篩選
函數(shù)奸柬。使用filter()
函數(shù),關(guān)鍵在于正確實(shí)現(xiàn)一個(gè)篩選
函數(shù)婴程。
計(jì)算素?cái)?shù)
計(jì)算素?cái)?shù)的一個(gè)方法是埃拉托色尼篩選法廓奕,理解非常簡(jiǎn)單:
列出從2
開(kāi)始的所有自然數(shù),構(gòu)造一個(gè)序列:
2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,......
取序列的第一個(gè)數(shù)2
档叔,它一定是素?cái)?shù)桌粉,然后用2
把序列的2
的倍數(shù)篩掉
3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,......
取序列的第一個(gè)數(shù)3
,它一定是素?cái)?shù)衙四,然后用3
把序列的3
的倍數(shù)篩掉
4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,......
取序列的第一個(gè)數(shù)5
铃肯,它一定是素?cái)?shù),然后用5
把序列的5
的倍數(shù)篩掉
6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,......
不斷的篩選下去传蹈,就可以得到所有的素?cái)?shù)缘薛。即:
構(gòu)造一個(gè)從3開(kāi)始的奇數(shù)序列,下列是一個(gè)從3開(kāi)始的奇數(shù)序列生成器卡睦,并且是一個(gè)無(wú)限序列3-∞
def _iter():
n = 1
while True:
n = n + 2
yield n
定義一個(gè)篩選函數(shù)
def _not_divisible(n):
return lambda x : x % n > 0 #序列x中的所有元素% n > 0的保留,% n == 0的不保留
最后漱抓,定義一個(gè)生成器表锻,不斷返回下一個(gè)素?cái)?shù)
def primes():
yield 2
it = _iter() # 初始化序列
while True:
n = next(it) # 返回序列的第一個(gè)數(shù)
yield n
it = filter(_not_divisible(n), it) # 構(gòu)造一個(gè)新的序列
primes是一個(gè)無(wú)限序列,所以調(diào)用時(shí)乞娄,設(shè)置一個(gè)退出循環(huán)的條件
for n in primes():
if n < 100:
print(n) #打印從2-100的所有素?cái)?shù)值瞬逊,即上述`primes()`函數(shù)中`yield`返回的n
else:
break
sorted排序算法
內(nèi)置的排序算法函數(shù)sorted()
,可以對(duì)list進(jìn)行排序仪或。sorted()
函數(shù)還可以接收一個(gè)key
函數(shù)來(lái)實(shí)現(xiàn)自定義的排序确镊。即:
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
# 按學(xué)生姓名進(jìn)行排序規(guī)則
def by_name_sort(t):
return t[0]
# 按照學(xué)生成績(jī)從高到低排序規(guī)則
def by_score_sort(t):
return -t[1]
# 運(yùn)行結(jié)果:
L2 = sorted(L, key=by_name_sort)
print(L2)
# 結(jié)果:[('Adam', 92), ('Bart', 66), ('Bob', 75), ('Lisa', 88)]
L3 = sorted(L, key=by_score_sort)
print(L3)
# 結(jié)果:[('Adam', 92), ('Lisa', 88), ('Bob', 75), ('Bart', 66)]
返回函數(shù)
將函數(shù)作為結(jié)果值返回:
def lazy_sum(*args) :
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum # 返回sum函數(shù),sum函數(shù)內(nèi)暫不計(jì)算求和
f = lazy_sum(1, 3, 5, 7, 9)
print(f)
# TODO:打臃渡尽:<function lazy_sum.<locals>.sum at 0x0000014B7A247A60>
print(f()) # 運(yùn)行f函數(shù)蕾域,即lazy_sum(*args)返回的sum()函數(shù)
# 打印結(jié)果:25
函數(shù)lazy_sum(*args)
中又定義了函數(shù)sum
,內(nèi)部函數(shù)sum
可以引用外部函數(shù)lazy_sum
的參數(shù)和局部變量,當(dāng)lazy_sum
返回函數(shù)sum
時(shí)旨巷,相關(guān)參數(shù)和變量都保存在返回的函數(shù)中巨缘,被稱(chēng)為“閉包”的程序擁有極大的威力。
注:每次調(diào)用時(shí)采呐,都會(huì)返回一個(gè)新的函數(shù)若锁,即使傳入相同的參數(shù)
>>> f1 = lazy_sum(1, 3, 5, 7, 9)
>>> f2 = lazy_sum(1, 3, 5, 7, 9)
>>> f1 == f2
False
>>> f1() == f2()
True
閉包
返回的函數(shù)并沒(méi)有立刻執(zhí)行,而是直到調(diào)用了f()
才執(zhí)行
def count():
fs = []
for i in range(1,4):
def f():
return i * i
fs.append(f)
return fs
f1, f2, f3 = count()
# 調(diào)用
print(f1) # 9
print(f2) # 9
print(f3) # 9
全部都是9
斧吐,原因在于返回的函數(shù)引用了變量i
又固,但是并非立刻執(zhí)行,等到3個(gè)函數(shù)都返回時(shí)煤率,引用的變量i
已經(jīng)變成了3
仰冠,因那次結(jié)果為9
。
返回閉包時(shí)牢記:返回函數(shù)不要引用任何循環(huán)變量涕侈,或者后續(xù)會(huì)發(fā)生變化的變量沪停。
若一定要引用循環(huán)變量,在創(chuàng)建一個(gè)函數(shù)裳涛,用函數(shù)的參數(shù)綁定循環(huán)變量當(dāng)前的值木张。
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)立刻被執(zhí)行,因此i的當(dāng)前值被傳入f()
return fs
f1, f2, f3 = count()
# 調(diào)用
print(f1) # 1
print(f2) # 4
print(f3) # 9
缺點(diǎn)是代碼較長(zhǎng)端三,可利用lambda函數(shù)縮短代碼舷礼。
匿名函數(shù)lambda
lambda x: x * x
實(shí)際上是:
def f(x):
return x * x
lambda
是匿名函數(shù)的關(guān)鍵字,冒號(hào)前面的x
表示函數(shù)參數(shù)郊闯。
匿名函數(shù)有個(gè)限制妻献,只能有一個(gè)表達(dá)式,不用寫(xiě)return
团赁,返回值就是該表達(dá)式的結(jié)果育拨。也可以將匿名函數(shù)賦值給一個(gè)變量,利用變量來(lái)調(diào)用該函數(shù)
裝飾器
函數(shù)也是一個(gè)對(duì)象欢摄,而且函數(shù)對(duì)象可以被賦值給變量熬丧,所以,通過(guò)變量也能調(diào)用該函數(shù)怀挠。函數(shù)對(duì)象有一個(gè)__name__
屬性析蝴,可以拿到函數(shù)的沒(méi)名字:
def now():
print('2020-06-05')
f = now
print(now.__name__) # 'now'
現(xiàn)增強(qiáng)now()
函數(shù)的功能,如绿淋,在函數(shù)調(diào)用前后自動(dòng)打印日志闷畸,但是不希望修改now()
函數(shù)的定義芳悲,這種在代碼運(yùn)行期間動(dòng)態(tài)增加功能的方式乎完,被稱(chēng)為“裝飾器”(Decorator
)。
decorator是一個(gè)返回函數(shù)的高階函數(shù)忍疾,定義一個(gè)能打印日志的decorator
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
函數(shù)log
是一個(gè)decorator,所以接受一個(gè)函數(shù)作為參數(shù)倘待,并返回一個(gè)函數(shù)疮跑。借助@
語(yǔ)法,把dedcorator置于函數(shù)的定義處:
@log
def now():
print('2020-06-05')
當(dāng)調(diào)用now()
函數(shù)凸舵,不僅會(huì)運(yùn)行now()
函數(shù)本身祖娘,還會(huì)在運(yùn)行now()
函數(shù)前打印一行日志:
>>> now()
calll now()
2020-06-05
將@log
放在now()
函數(shù)的定義處,相當(dāng)于執(zhí)行了語(yǔ)句now = log(now)
由于log()
是一個(gè)decorator啊奄,返回一個(gè)函數(shù)渐苏,所以,原來(lái)的now()
函數(shù)任然存在菇夸,只是同名的now
變量指向了新的函數(shù)琼富,于是調(diào)用now()
將執(zhí)行新函數(shù),即在log()
函數(shù)中返回的wrapper()
函數(shù)庄新。
wrapper()
函數(shù)的參數(shù)定義是(*args, **kw)
鞠眉,因此,wrapper()
函數(shù)課可以接受任意參數(shù)的調(diào)用择诈。在wrapper()
函數(shù)內(nèi)械蹋,首先打印日志,再調(diào)用原始函數(shù)羞芍。
若decorator本身需要傳入?yún)?shù)哗戈,就需要編寫(xiě)一個(gè)返回decorator的高階函數(shù),寫(xiě)出來(lái)會(huì)更加復(fù)雜荷科,如:自定義log的文本:
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
使用后:
@log('execute')
def now():
print('2020-06-05')
now()
# execute now():
# 2020-06-05
3層嵌套的效果是這樣的唯咬,now = log('execute')(now)
。首先執(zhí)行log('execute')
畏浆,返回的是decorator
函數(shù)胆胰,再調(diào)用返回的函數(shù),參數(shù)是now
函數(shù)刻获,返回值最終是wrapper
函數(shù)煮剧。
函數(shù)也是對(duì)象,它有__name__
等屬性将鸵,經(jīng)過(guò)哦decorator裝飾之后,'__name__'
屬性從原來(lái)的函數(shù)名變成了'wrapper'
佑颇。因?yàn)榉祷氐哪莻€(gè)wrapper()
函數(shù)名字就是wrapper
顶掉,所以,幼把原始函數(shù)的__name__
等屬性復(fù)制到wrapper()
函數(shù)中挑胸,否則痒筒,有些依賴(lài)函數(shù)簽名的代碼執(zhí)行會(huì)出錯(cuò)。
python中內(nèi)置了funtools.wraps
,就是干這個(gè)事情的:
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('call %s()' % func.__name__)
return func(*args, **kw)
return wrapper
import functools
是導(dǎo)入functools
模塊簿透。
偏函數(shù)
偏函數(shù)移袍,即functools
模塊中的一個(gè)功能函數(shù)partial
,functools.partial
的作用就是老充,把一個(gè)函數(shù)的某些參數(shù)給固定住(即默認(rèn)值)葡盗,返回一個(gè)新的函數(shù),調(diào)用這個(gè)新函數(shù)會(huì)更加簡(jiǎn)單啡浊。
當(dāng)函數(shù)的參數(shù)太多觅够,需要簡(jiǎn)化時(shí),使用functools.partial
創(chuàng)建一個(gè)新的函數(shù)巷嚣,新函數(shù)可以固定原函數(shù)部分參數(shù)喘先,從而調(diào)用時(shí)更簡(jiǎn)單。