復盤廖雪峰教程中的要點
1. 函數(shù)式編程 > 高階函數(shù)
map() 函數(shù)接收兩個參數(shù)诵次,一個是函數(shù)翠拣,一個是Iterable,map將傳入的函數(shù)依次作用到序列的每個元素,并把結果作為新的Iterator 迭代器 返回滚朵。結果是一個惰性序列蛉拙,一般我們可以用
list()
把它變成列表讀取宙攻。
>>> def f(x): # map 特別適合對內部每個元素都需要作用的地方画舌。
... 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]
reduce()
reduce把一個函數(shù)作用在一個序列
[x1, x2, x3, ...]
上堕担,這個函數(shù)必須接收兩個參數(shù),reduce
把結果繼續(xù)和序列的下一個元素做累積計算曲聂,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
應該適合在一些累計求和中去用霹购,得加這句from functools import reduce
fliter()
該函數(shù)為內置,和
map()
類似,filter()
也接收一個函數(shù)和一個序列朋腋。和map()
不同的是齐疙,filter()
把傳入的函數(shù)依次作用于每個元素,然后根據返回值是True還是False決定保留還是丟棄該元素
def is_odd(n):
return n % 2 == 1
list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 結果: [1, 5, 9, 15]
sorted()
Python內置的
sorted()
函數(shù)就可以對list進行排序
>>> sorted([36, 5, -12, 9, -21], key=abs) # 當一個參數(shù)時旭咽,可以將數(shù)字按大小排列贞奋,兩個值時,按照key排序轻专,這里abs是求絕對值
[5, 9, -12, -21, 36]
返回函數(shù)(難點) - 閉包
高階函數(shù)除了可以接受函數(shù)作為參數(shù)外忆矛,還可以把函數(shù)作為結果值返回。
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
當我們調用lazy_sum()
時请垛,返回的并不是求和結果催训,而是求和函數(shù):
>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f
<function lazy_sum.<locals>.sum at 0x101c6ed90>
調用函數(shù)f
時,才真正計算求和的結果:
>>> f()
25
在這個例子中宗收,我們在函數(shù)lazy_sum
中又定義了函數(shù)sum漫拭,并且,內部函數(shù)sum可以引用外部函數(shù)lazy_sum的參數(shù)和局部變量混稽,當lazy_sum
返回函數(shù)sum時采驻,相關參數(shù)和變量都保存在返回的函數(shù)中,這種稱為“閉包(Closure)”的程序結構擁有極大的威力匈勋。
請再注意一點礼旅,當我們調用
lazy_sum()
時,每次調用都會返回一個新的函數(shù)洽洁,即使傳入相同的參數(shù):
>>> f1 = lazy_sum(1, 3, 5, 7, 9)
>>> f2 = lazy_sum(1, 3, 5, 7, 9)
>>> f1==f2
False # f1()和f2()的調用結果互不影響
注意到返回的函數(shù)在其定義內部引用了局部變量args
痘系,所以,當一個函數(shù)返回了一個函數(shù)后饿自,其內部的局部變量還被新函數(shù)引用汰翠,所以,閉包用起來簡單昭雌,實現(xiàn)起來可不容易复唤。
另一個需要注意的問題是,返回的函數(shù)并沒有立刻執(zhí)行烛卧,而是直到調用了f()
才執(zhí)行佛纫。我們來看一個例子:
def count():
fs = []
for i in range(1, 4): # 循環(huán)的函數(shù)
def f():
return i*i # G:循環(huán)返回時,由于它優(yōu)先執(zhí)行,所以當循環(huán)1時呈宇,代碼不往下跟磨,而是繼續(xù)在內部直到循環(huán)到3,函數(shù)會再次賦值攒盈,形成新的函數(shù)
fs.append(f)
return fs
f1, f2, f3 = count()
結果如下:
>>> f1() # 為什么結果不是1,4哎榴,9 型豁? 原因就在于返回的函數(shù)引用了變量i,但它并非立刻執(zhí)行尚蝌。等到3個函數(shù)都返回時迎变,它們所引用的變量i已經變成了3,因此最終結果為9
9 # G:個人理解當它循環(huán)1的時候飘言,返回的是一個函數(shù)衣形,當它循環(huán)到2時,已經變成了另一個函數(shù)姿鸿,雖然函數(shù)表達式一樣谆吴,但已經不是一家人了
>>> f2() # 所以,當它循環(huán)3時苛预,這時又是一個函數(shù)句狼,它內部只有3這個值,結果當然是9
9
>>> f3()
9
>>> f5, f6 = count()
ValueError: too many values to unpack (expected 2)
>>> f7 = count()
>>> f7()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-183-6e06e95ee15c> in <module>()
----> 1 f7()
TypeError: 'list' object is not callable
>>> f7 # ??從這里可以看出count()就是三個函數(shù)組成的 list 啊~
[<function __main__.count.<locals>.f>,
<function __main__.count.<locals>.f>,
<function __main__.count.<locals>.f>]
教訓:返回閉包時牢記的一點就是:返回函數(shù)不要引用任何循環(huán)變量热某,或者后續(xù)會發(fā)生變化的變量腻菇。
如果一定要引用循環(huán)變量怎么辦?方法是再創(chuàng)建一個函數(shù)昔馋,用該函數(shù)的參數(shù)綁定循環(huán)變量當前的值筹吐,無論該循環(huán)變量后續(xù)如何更改,已綁定到函數(shù)參數(shù)的值不變:
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的當前值被傳入f()
return fs
再看看結果:
>>> f1, f2, f3 = count() # ??count()應該是一個函數(shù)組成的list,所以必須用f1,f2,f3這樣對應數(shù)量的變量來實現(xiàn)拆包丘薛,否則即報錯
>>> f1()
1
>>> f2()
4
>>> f3()
9
>>> f4 = count() # ??思考一下,下面的變化
>>> f4 # ??從這里可以看出count()就是三個函數(shù)組成的 list 啊~
[<function __main__.count.<locals>.f.<locals>.g>,
<function __main__.count.<locals>.f.<locals>.g>,
<function __main__.count.<locals>.f.<locals>.g>]
>>> f4()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-168-6e06e95ee15c> in <module>()
----> 1 f4()
TypeError: 'list' object is not callable
>>> f1
1
以上再??注意一點垄提,上述2段代碼榔袋, fs.append(f)
fs.append(f(i))
,對于函數(shù)f(x)來說,引用f(x)
就是完整的函數(shù)式铡俐,跟數(shù)學一樣凰兑,會立即執(zhí)行,而引用 f
审丘,這里只是一個指代函數(shù)的變量吏够,所以不會立即執(zhí)行,而是等待執(zhí)行順序優(yōu)先的參數(shù)再次改變它,形成新函數(shù)锅知。f(x)一個返回值播急,f一個是返回的函數(shù)
匿名函數(shù) lambda
當我們在傳入函數(shù)時,有些時候售睹,不需要顯式地定義函數(shù)桩警,直接傳入匿名函數(shù)更方便
>>> 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表示匿名函數(shù),冒號前面的x表示函數(shù)參數(shù)昌妹。
匿名函數(shù)有個限制捶枢,就是只能有一個表達式,不用寫return飞崖,返回值就是該表達式的結果烂叔。
用匿名函數(shù)有個好處,因為函數(shù)沒有名字固歪,不必擔心函數(shù)名沖突蒜鸡。此外,匿名函數(shù)也是一個函數(shù)對象牢裳,也可以把匿名函數(shù)賦值給一個變量逢防,再利用變量來調用該函數(shù).
裝飾器
顧名思義,裝飾函數(shù)使用的
由于函數(shù)也是一個對象贰健,而且函數(shù)對象可以被賦值給變量胞四,所以,通過變量也能調用該函數(shù)伶椿。
>>> def now():
... print('2015-3-25')
...
>>> f = now # 對于 now()函數(shù)辜伟,now()表示是函數(shù)的結果,即執(zhí)行了過程了脊另,而 now 只是函數(shù)的對象导狡,注意區(qū)分
>>> f()
2015-3-25
函數(shù)對象有一個__name__
屬性,可以拿到函數(shù)的名字:
>>> now.__name__
'now'
>>> f.__name__
'now'
現(xiàn)在偎痛,假設我們要增強now()
函數(shù)的功能旱捧,比如,在函數(shù)調用前后自動打印日志踩麦,但又不希望修改now()
函數(shù)的定義枚赡,這種在代碼運行期間動態(tài)增加功能的方式,稱之為“裝飾器”(Decorator)谓谦。
在面向對象(OOP)的設計模式中贫橙,decorator被稱為裝飾模式。OOP的裝飾模式需要通過繼承和組合來實現(xiàn)反粥,而Python除了能支持OOP的decorator外卢肃,直接從語法層次支持decorator疲迂。Python的decorator可以用函數(shù)實現(xiàn),也可以用類實現(xiàn)莫湘。
重
點
尤蒿。
待
繼
續(xù)
學
習
decorator可以增強函數(shù)的功能,定義起來雖然有點復雜幅垮,但使用起來非常靈活和方便腰池。
偏函數(shù)
Python的
functools
模塊提供了很多有用的功能,其中一個就是偏函數(shù)(Partial function)忙芒。要注意巩螃,這里的偏函數(shù)和數(shù)學意義上的偏函數(shù)不一樣
nt()函數(shù)可以把字符串轉換為整數(shù),但int()
函數(shù)還提供額外的base參數(shù),默認值為10匕争。如果傳入base參數(shù),就可以做N進制的轉換:
>>> int('12345', base=8)
5349
>>> int('12345', 16)
74565
假設要轉換大量的二進制字符串爷耀,每次都傳入int(x, base=2)
非常麻煩甘桑,于是,我們想到歹叮,可以定義一個int2()的函數(shù)跑杭,默認把base=2
傳進去:
def int2(x, base=2):
return int(x, base)
這樣,我們轉換二進制就非常方便了:
>>> int2('1000000')
64
>>> int2('1010101')
85
functools.partial
就是幫助我們創(chuàng)建一個偏函數(shù)的咆耿,不需要我們自己定義int2()
德谅,可以直接使用下面的代碼創(chuàng)建一個新的函數(shù)int2
:
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85
所以,簡單總結functools.partial
的作用就是萨螺,把一個函數(shù)的某些參數(shù)給固定渍觥(也就是設置默認值)雅采,返回一個新的函數(shù)丝蹭,調用這個新函數(shù)會更簡單赚瘦。