函數(shù)式編程:把函數(shù)作為參數(shù)傳入,這樣的函數(shù)稱為高階函數(shù),函數(shù)式編程就是指這種高度抽象的編程范式。
def add(x, y, f):
return f(x) + f(y)
print(add(-5, 6, abs))
11
map/reduce
-
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。
-
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
利用map和reduce寫一個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
用lambda函數(shù)(匿名函數(shù))進(jì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))
- 利用map()函數(shù),把用戶輸入的不規(guī)范的英文名字,變?yōu)槭鬃帜复髮?,其他小寫的?guī)范名字。輸入:['adam', 'LISA', 'barT'],輸出:['Adam', 'Lisa', 'Bart']:
L = ['adam', 'LISA', 'barT','AAAAAA']
def normalize(name):
return name[0].upper{} + name[1:].lower()
print(list (normalize(L)))
輸出結(jié)果:['Adam', 'Lisa', 'Bart']
- 利用map和reduce編寫一個str2float函數(shù),把字符串'123.456'轉(zhuǎn)換成浮點數(shù)123.456:
def str2Int(s):
return {'0' : 0, '1' : 1, '2' : 2, '3' : 3, '4' : 4, '5' : 5, '6' : 6, '7' : 7, '8' : 8, '9' : 9}[s]
def str2float(name):
if name.find(".") == -1:
return reduce(lambda x, y: x * 10 + y, map(str2Int, name))
else:
n = name.index('.')
s = name[:n] + name[n + 1:]
return reduce(lambda x, y: x * 10 + y, map(str2Int, s)) / (10 ** (len(name) - (n + 1)))
print(str2float('12332.1'))
#輸出結(jié)果:12332.1
filter()函數(shù)用于過濾序列。
和
map()類似,filter()也接收一個函數(shù)和一個序列。和map()不同的是,filter()把傳入的函數(shù)依次作用于每個元素,然后根據(jù)返回值是True還是False決定保留還是丟棄該元素。
例如,在一個list中,刪掉偶數(shù),只保留奇數(shù),可以這么寫:
def is_odd(n):
return n % 2 == 1
filter(is_odd, [1,2,3,4,5,6,7,8,9])
# 結(jié)果: [1, 5, 9, 15]
回數(shù)是指從左向右讀和從右向左讀都是一樣的數(shù),例如12321,909。請利用filter()濾掉非回數(shù):
def is_palindrome(n):
m = str(n)
if m == m[::-1]:
return n
output = filter(is_palindrome, range(0,90))print(list(output))
sorted排序算法
Python內(nèi)置的sorted()函數(shù)就可以對list進(jìn)行排序:
>>> 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]
現(xiàn)在,我們提出排序應(yīng)該忽略大小寫,按照字母序排序。要實現(xiàn)這個算法,不必對現(xiàn)有代碼大加改動,只要我們能用一個key函數(shù)把字符串映射為忽略大小寫排序即可。忽略大小寫來比較兩個字符串,實際上就是先把字符串都變成大寫(或者都變成小寫),再比較。
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']
要進(jìn)行反向排序,不必改動key函數(shù),可以傳入第三個參數(shù)reverse=True
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']
假設(shè)我們用一組tuple表示學(xué)生名字和成績:L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)],請用sorted()對上述列表分別按名字排序:
def by_name(t):
return t[0]
L = [('Bob', 75), ('Adam', 92), ('Bart', 66),('Lisa',88)]
print(sorted(L,key = by_name))
#[('Adam', 92), ('Bart', 66), ('Bob', 75), ('Lisa', 88)]
def by_score(t):
return t[1]
L = [('Bob', 75), ('Adam', 92), ('Bart', 66),('Lisa',88)]
print(sorted(L,key = by_score))
#[('Bart', 66), ('Bob', 75), ('Lisa', 88), ('Adam', 92)]
返回函數(shù)
函數(shù)作為返回值高階函數(shù)除了可以接受函數(shù)作為參數(shù)外,還可以把函數(shù)作為結(jié)果值返回。
我們來實現(xiàn)一個可變參數(shù)的求和。通常情況下,求和的函數(shù)是這樣定義的:
def calc_sum(*args):
ax = 0
for n in args:
ax = ax + n
return ax
但是,如果不需要立刻求和,而是在后面的代碼中,根據(jù)需要再計算怎么辦?可以不返回求和的結(jié)果,而是返回求和的函數(shù):
def lazy_sum(*args):
def sum():
ax = 0 for n in args:
ax = ax + n
return ax
return sum
當(dāng)我們調(diào)用lazy_sum()時,返回的并不是求和結(jié)果,而是求和函數(shù):
>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f<function lazy_sum.<locals>.sum at 0x101c6ed90>
調(diào)用函數(shù)f時,才真正計算求和的結(jié)果:
>>> f()
25
匿名函數(shù):lambda
>>> 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+y ,lambda x,y:x+y與lambda x=x,y=y:x+y 他們?nèi)叩膮^(qū)別是啥???
#lambda :x+y匿名函數(shù)內(nèi)部沒有參數(shù):
def build(x, y):
return lambda: x * x + y * y
print(build(2,4)())
#20
#等價于:
def fn(x,y):
def lam():
return x * x + y * y
return lam
print(fn(2,4)())
#20
#lambda x,y:x+y參數(shù)傳入匿名函數(shù)內(nèi)部
>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]
#類似于:
def f(x):
return x * x
lambda x=x,y=y:x+y這里需要看官方文檔是怎么講的:
>>> squares = []
>>> for x in range(5):
... squares.append(lambda n=x: n**2)
Here, n=x creates a new variable n local to the lambda and computed when the lambda is defined so that it has the same value that x had at that point in the loop. This means that the value of n will be 0 in the first lambda, 1 in the second, 2 in the third, and so on. Therefore each lambda will now return the correct result:
(大體的意思是說,執(zhí)行的時候會創(chuàng)建一個本地的n,n的值和x的值相等)
官方文檔出處
>>> squares[2]()
4
>>> squares[4]()
16
裝飾器
>>> def now():
... print('2015-3-25')
...
>>> f = now
>>> f()
2015-3-25
函數(shù)對象有一個name屬性,可以拿到函數(shù)的名字:
>>> now.__name__
'now'
>>> f.__name__
'now'
現(xiàn)在,假設(shè)我們要增強now()函數(shù)的功能,比如,在函數(shù)調(diào)用前后自動打印日志,但又不希望修改now()函數(shù)的定義,這種在代碼運行期間動態(tài)增加功能的方式,稱之為“裝飾器”(Decorator)。
本質(zhì)上,decorator就是一個返回函數(shù)的高階函數(shù)。所以,我們要定義一個能打印日志的decorator,可以定義如下:
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):#args和kw是func的參數(shù)
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
觀察上面的log,因為它是一個decorator,所以接受一個函數(shù)作為參數(shù),并返回一個函數(shù)。我們要借助Python的@語法,把decorator置于函數(shù)的定義處:
@log
def now():
print('2015-3-25')
調(diào)用now()函數(shù),不僅會運行now()函數(shù)本身,還會在運行now()函數(shù)前打印一行日志:
>>> now()
call now():
2015-3-25
如果decorator本身需要傳入?yún)?shù),那就需要編寫一個返回decorator的高階函數(shù),寫出來會更復(fù)雜。比如,要自定義log的文本:
import functools
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
偏函數(shù)
當(dāng)函數(shù)的參數(shù)個數(shù)太多,需要簡化時,使用functools.partial
可以創(chuàng)建一個新的函數(shù),這個新函數(shù)可以固定住原函數(shù)的部分參數(shù),從而在調(diào)用時更簡單。
在介紹函數(shù)參數(shù)的時候,我們講到,通過設(shè)定參數(shù)的默認(rèn)值,可以降低函數(shù)調(diào)用的難度。而偏函數(shù)也可以做到這一點。舉例如下:
int()函數(shù)可以把字符串轉(zhuǎn)換為整數(shù),當(dāng)僅傳入字符串時,int()函數(shù)默認(rèn)按十進(jìn)制轉(zhuǎn)換:
>>> int('12345')
12345
但int()函數(shù)還提供額外的base參數(shù),默認(rèn)值為10。如果傳入base
參數(shù),就可以做N進(jìn)制的轉(zhuǎn)換:
>>> int('12345', base=8)
5349
>>> int('12345', 16)
74565
假設(shè)要轉(zhuǎn)換大量的二進(jìn)制字符串,每次都傳入int(x, base=2)非常麻煩,于是,我們想到,可以定義一個int2()的函數(shù),默認(rèn)把base=2
傳進(jìn)去:
def int2(x, base=2):
return int(x, base)
這樣,我們轉(zhuǎn)換二進(jìn)制就非常方便了:
>>> 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
所以,簡單總結(jié)functools.partial的作用就是,把一個函數(shù)的某些參數(shù)給固定?。ㄒ簿褪窃O(shè)置默認(rèn)值),返回一個新的函數(shù),調(diào)用這個新函數(shù)會更簡單。
創(chuàng)建偏函數(shù)時,實際上可以接收函數(shù)對象、args和*kw這3個參數(shù),當(dāng)傳入:
int2 = functools.partial(int, base=2)
實際上固定了int()函數(shù)的關(guān)鍵字參數(shù)base,也就是:
int2('10010')
相當(dāng)于:
kw = { 'base': 2 }
int('10010', **kw)
當(dāng)傳入:
max2 = functools.partial(max, 10)
實際上會把10作為*args的一部分自動加到左邊,也就是:
max2(5, 6, 7)
相當(dāng)于:
args = (10, 5, 6, 7)
max(*args)
結(jié)果為10。