本篇文章我們來介紹下Python函數式編程的知識。最主要的一點,Python中的函數是對象,可以復制給變量!好了,我們來介紹幾個Python函數式編程中的要點,包括高階函數、匿名函數、裝飾器、偏函數等等。精彩內容,不容錯過!
1、高階函數
函數本身也可以賦值給變量,即:變量可以指向函數。如果一個變量指向了一個函數,那么,可以通過該變量來調用這個函數。
f = abs
f(-10) # 10
既然變量可以指向函數,函數的參數能接收變量,那么一個函數就可以接收另一個函數作為參數,這種函數就稱之為高階函數。
def add_abs(x,y,f):
return f(x) + f(y)
f = abs
add_abs(-5,6,f) # 11
接下來,我們介紹幾個重點的高階函數。
map函數
map()函數接收兩個參數,一個是函數,一個是Iterable,map將傳入的函數依次作用到序列的每個元素,并把結果作為新的Iterator返回。我們之前介紹過了,Iterator是惰性序列,需要通過list()函數讓它把返回結果變為list。
def f(x):
return x * x
r = list(map(f,[1,2,3,4,5,6,7,8,9,10]))
r
輸出結果為:
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
reduce函數
reduce把一個函數作用在一個序列[x1, x2, x3, ...]上,這個函數必須接收兩個參數,reduce把結果繼續和序列的下一個元素做累積計算:
def f(x,y):
return x + y
reduce(f,[1,2,3,4,5]) # 15
filter函數
和map()類似,filter()也接收一個函數和一個序列。和map()不同的是,filter()把傳入的函數依次作用于每個元素,然后根據返回值是True還是False決定保留還是丟棄該元素。filter()函數返回的是一個Iterator,也就是一個惰性序列,同樣需要通過list()函數讓它把返回結果變為list。
def is_odd(n):
return n%2==1
list(filter(is_odd,[1,2,3,4,5,6,7,8,9]))
結果為:
[1, 3, 5, 7, 9]
sorted函數
sorted()函數也是一個高階函數,它還可以接收一個key函數來實現自定義的排序,例如按絕對值大小排序或者按照小寫字母順序進行排序:
print(sorted([36,5,-12,9,-21],key=abs))
print(sorted(['bob', 'about', 'Zoo', 'Credit'],key=str.lower))
結果為:
[5, 9, -12, -21, 36]
['about', 'bob', 'Credit', 'Zoo']
2、函數作為返回值
高階函數除了可以接受函數作為參數外,還可以把函數作為結果值返回。
我們來實現一個可變參數的求和,如果不需要立刻求和,而是在后面的代碼中,根據需要再計算怎么辦?可以不返回求和的結果,而是返回求和的函數:
def lazy_sum(*args):
def sum():
res = 0
for n in args:
res += n
return res
return sum
f = lazy_sum(1,3,5,7,9)
f()
在這個例子中,我們在函數lazy_sum中又定義了函數sum,
并且,內部函數sum可以引用外部函數lazy_sum的參數和局部變量,
當lazy_sum返回函數sum時,相關參數和變量都保存在返回的函數中,
這種稱為“閉包(Closure)”的程序結構擁有極大的威力。
請再注意一點,當我們調用lazy_sum()時,每次調用都會返回一個新的函數,即使傳入相同的參數:
f1 = lazy_sum(1,3,5,7,9)
f2 = lazy_sum(1,3,5,7,9)
f1 == f2 # False
另一個需要注意的問題是,返回的函數并沒有立刻執行,而是直到調用了f()才執行。我們來看一個例子:
def count():
fs = []
for i in range(1,4):
def f():
return i * i
fs.append(f)
return fs
f1,f2,f3 = count()
print(f1())
print(f2())
print(f3())
輸出結果為:
9
9
9
全部都是9!原因就在于返回的函數引用了變量i,但它并非立刻執行。等到3個函數都返回時,它們所引用的變量i已經變成了3,因此最終結果為9。如果一定要引用循環變量怎么辦?方法是再創建一個函數,用該函數的參數綁定循環變量當前的值,無論該循環變量后續如何更改,已綁定到函數參數的值不變:
def count():
fs = []
def sub(j):
def f():
return j * j
return f
for i in range(1,4):
fs.append(sub(i))
return fs
f1,f2,f3 = count()
print(f1())
print(f2())
print(f3()
結果為:
1
4
9
3、匿名函數lambda
當我們在傳入函數時,有些時候,不需要顯式地定義函數,直接傳入匿名函數更方便。相信大家對于匿名函數一定不陌生,其實就是我們常說的lambda函數:
list(map(lambda x:x * x,[1,2,3,4,5,6,7,8,9]))
def build(x,y):
return lambda:x * x + y * y
reduce(lambda x,y:x + y,[1,2,3,4,5,6,7,8,9])
4、裝飾器
在代碼運行期間動態增加功能的方式,稱之為“裝飾器”(Decorator)。
本質上,decorator就是一個返回函數的高階函數。
所以,假設我們要定義一個能夠打印當前函數名的decorator:
def log(func):
def wrapper(*args,**kwargs):
print('call %s()' % func.__name__)
return func(*args,**kwargs)
return wrapper
@log
def helloworld():
print('hello world')
helloworld()
執行結果為:
call helloworld()
hello world
下面的例子中,正是由于wrapper中把 func(args,*kwargs)進行return,因此函數得以執行。
如果decorator本身需要傳入參數,那就需要編寫一個返回decorator的高階函數,寫出來會更復雜。比如,要自定義log的文本:
def log(text):
def decorator(func):
def wrapper(*args,**kwargs):
print('%s %s():' % (text,func.__name__))
return func(*args,**kwargs)
return wrapper
return decorator
@log('execute')
def helloworld():
print('hello world')
helloworld()
上面代碼的執行過程相當于helloworld = log('execute')(helloworld)
我們講了函數也是對象,它有name等屬性,但你去看經過decorator裝飾之后的函數,它們的name已經從原來的'helloworld'變成了'wrapper':
helloworld.__name__ #'wrapper'
如果需要把原始函數的name等屬性復制到wrapper()函數中,使用Python內置的functools.wraps函數,代碼如下:
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
@log
def helloworld():
print('hello world')
helloworld.__name__
此時的輸出就變為了'helloworld'
5、偏函數
一般的函數有許多需要定義的參數,假設我們想要固定其中的某些參數,返回一些新的函數,我們就可以使用functools.partial幫助我們創建一個偏函數,從而使得調用變得簡單
import functools
int2 = functools.partial(int, base=2)
int2('10010') # 18
當然我們也可以穿入一個函數字典:
kw = { 'base': 2 }
int('10010', **kw)
當傳入的參數沒有對應的key時,它默認時作為*args的一部分自動加到左邊,因此下面的函數相當于比較max(10,5,6,7),返回10:
max2 = functools.partial(max, 10)
max2(5, 6, 7)