④Python函數式程式設計
- 高階函數
- map
- reduce
- map與reduce結合使用
- filter
- sorted
- 傳回函數
- 匿名函數
- 裝飾器
- 偏函數
函數式程式設計的一個特點是,允許把函數本身作為參數傳入另一個函數,還允許傳回一個函數!
高階函數
高階函數有如下幾個特征,逐漸深入了解。
- 變量可以指向函數
>>> abs(-10)
10
>>> abs
<built-function abs>
>>> x=abs(-10)
>>> x
10
>>> f = abs
>>> f(-10)
10
如果把函數本身指派給變量,即便相知相函數,那麼可以通過變量來調用函數。
-
函數名也是變量
這裡僅僅說明,函數名可以當作變量使用,但是實際程式設計過程中,基本不會将函數名當成變量來使用。
>>> abs=10
>>> abs
10
-
傳入函數
變量可以指向函數,函數的參數可以接受變量,那麼自然而然一個函數也能接受另一個函數作為參數,這種函數就稱之為高階函數。
def add(x,y,f):
return f(x)+f(y)
上面定義的函數可以通過
add(-5,6,abs)
這種方式調用。
map
接收兩個參數,一個是函數,一個是
Iterable
,
map
将傳入的函數依次作用到序列的每個元素,并把結果作為新的
Iterator
傳回。
舉例說明:
>>> def square_func(x):
return x*x
>>> r=map(square_func,[1,2,3,4,5])
>>> list(r)
[1,4,9,16,25]
map
傳回的是惰性序列,是以通過
list()
函數把整個序列都計算出來并傳回一個list。
實際上
map()
作為高階函數,将運算規則抽象了,是以,我們不但可以計算簡單的求平方的函數,還可以計算任意複雜的函數。例如将一個list所有數字都轉化為字元串。
>>> list(map(str,[1,2,3,4,5,6,7,8,9]))
['1', '2', '3', '4', '5', '6', '7', '8', '9']
reduce
reduce
把一個函數作用在一個序列上,這個函數必須接受兩個參數,
reduce
将結果繼續和序列的下一個元素做累計計算,其效果就是:
reduce(f,[x1,x2,x3,x4]) = f(f(f(x1,x2),x3),x4)
例如對一個序列進行求和就可以使用
reduce
實作:
from functools import reduce
def add(x,y):
return x+y
r = reduce(add,[1,2,3,4])
print(r)#result:10
map與reduce結合使用
map可以與reduce實作更多的功能,甚至可以和
lambda
表達式結合使用。
from functools import reduce
DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
def char2num(s):
return DIGITS[s]
def str2int(s):
return reduce(lambda x, y: x * 10 + y, map(char2num, s))
上面的代碼就可以實作将字元串轉化為整數的功能。
filter
Python内建的
filter()
函數用于過濾序列。
和
map()
函數類似,
filter()
也接受一個函數和一個序列,但是和
map()
不同的是,
filter()
把傳入的函數依次作用于每個元素,然後根據傳回值是
True
和
False
決定保留還是丢棄該元素。
def is_odd(n)
return n % 2 ==1
sorted
排序是程式中經常會使用的功能,無論使用冒泡排序還是快速排序,排序的核心是比較兩個元素的大小。如果是數字,可以直接比較,但是如果是字元串和字典,直接比較數學上的大小是沒有意義的,此時必須通過函數抽象出來。
大家所熟知的
sorted()
函數其實也是個高階函數,可以接受一個
key
函數來實作自定義的排序。
>>> sorted([36,5,-12,9,-21],key=abs)
[5,9,-12,-21,36]
key指定的函數會作用在list的每個元素上,并根據key函數傳回的結果進行排序。
高階函數的抽象能力非常強,而且,核心代碼可以保持的非常簡潔。
傳回函數
函數作為傳回值,高階函數除了可以接受函數作為參數外,還可以把函數作為結果值傳回。
可以通過傳回凡是來定義一個求和函數:
def lazy_sum(*args):
def sum():
ax=0
for n in args:
ax = ax + n
return ax
return sum
>>> f =lazy_sum(*args)
>>> f
<function lazy_sum.<locals>.sum at 0x101c6ed90>
>>> f()#調用函數,産生結果
當
lazy_sum
函數傳回函數
sum
時,相關參數和變量都儲存在傳回的函數中,這種稱為“閉包”(Closure)的程式結構擁有極大的威力。
lazy_sum
函數的每次調用都會傳回一個新的函數,即使傳入相同的參數。
-
閉包
傳回閉包時注意一點:傳回函數不要引用任何循環變量,或者後續會發生變化的變量。
比如下面這種情況就是錯誤的:
def count():
fs=[]
for i in range(1,4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()
>>> f1()
9
>>> f2()
9
>>> f3()
本以為會傳回1,4,9但是實際上會傳回9,9,9。這是因為等到3個函數都傳回時,他們所引用的變量
i
已經變成
3
,是以最終結果為
9
。
為了解決這個問題,可以再建立一個函數,用該函數的參數綁定循環變量目前的值。
def count():
def f(j):
def g():
return j*j
return g
fs=[]
for i in range(1,4):
fs.append(f(i))
return
匿名函數
也就是常說的
lambda
表達式,一定程度上,匿名函數可以簡化代碼的書寫,但是也會增加代碼的了解難度。
list(map(lambda x:x*x,[1,2,3,4,5,6,7,8,9]))
lambda
表示匿名函數。
匿名函數沒有函數名,不需要擔心函數沖突,同時匿名函數是一個函數對象,可以把匿名函數指派給一個變量,再利用變量來調用該函數。
裝飾器
由于函數也是一個對象,而且函數對象可以被賦給變量,是以,通過變量也能調用該函數。
def now():
print('5/14/2020')
>>> f=now
>>> f()
函數對象有一個
__name__
屬性,可以拿到函數名字。
>>> now.__name__
now
假設現在,要增強
now()
函數的功能,比如,在函數調用前後自動列印日志,但又不希望修改
now()
函數定義,這種在代碼運作期間動态增加功能的方法,稱之為“裝飾器”。
def log(func):
def wrapper(*args,**kw):
print('call %s()'%func.__name__)
return func(*args,**kw)
return
上面的
log
,因為是一個decorator,是以接受一個函數作為參數,并傳回一個函數。借助Python的@羽凡,把decorator置于函數的定義處:
@log
def now():
print('5/14/2020')
>>> now()
call now():
5/14/2020
把
@log
放到
now()
函數的定義處,相當于執行了語句:
now=log(now)
由于
log()
是一個decorator,傳回一個函數,是以,原來的
now()
函數仍然存在,隻是現在同名的
now
變量指向了新的函數,于是調用
now()
将執行新函數,即在
log()
函數中傳回的
wrapper()
函數。
wrapper()
函數的參數定義是
(*args, **kw)
,是以,
wrapper()
函數可以接受任意參數的調用。在
wrapper()
函數内,首先列印日志,再緊接着調用原始函數。
如果decorator本身需要傳入參數,那就需要編寫一個傳回decorator的高階函數,寫出來會更加複雜。
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('2015-3-25')
執行結果如下:
>>> now()
execute now():
2015-3-25
和兩層嵌套的decorator相比,3層嵌套的效果是這樣的:
>>> now = log('execute')(now)
我們來剖析上面的語句,首先執行
log('execute')
,傳回的是
decorator
函數,再調用傳回的函數,參數是
now
函數,傳回值最終是
wrapper
函數。
以上兩種decorator的定義都沒有問題,但還差最後一步。因為我們講了函數也是對象,它有
__name__
等屬性,但你去看經過decorator裝飾之後的函數,它們的
__name__
已經從原來的
'now'
變成了
'wrapper'
:
>>> now.__name__
'wrapper'
因為傳回的那個
wrapper()
函數名字就是
'wrapper'
,是以,需要把原始函數的
__name__
等屬性複制到
wrapper()
函數中,否則,有些依賴函數簽名的代碼執行就會出錯。
不需要編寫
wrapper.__name__ = func.__name__
這樣的代碼,Python内置的
functools.wraps
就是幹這個事的,是以,一個完整的decorator的寫法如下:
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return
或者針對帶參數的decorator:
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
偏函數
import functools
int2 = functools.partial(int,base=2)#按照二進制進行轉換,轉換成int數字