高階函數(shù)
變量可以指向函數(shù)
>>> abs(-1)
1
>>> f = abs
>>> f(-1)
1
>>>
- 變量f指向了abs函數(shù)本身。直接調(diào)用abs()函數(shù)和調(diào)用變量f()完全相同。
函數(shù)名也是變量
>>> abs = 1
>>> abs (-1)
Traceback (most recent call last):
File "<pyshell#4>", line 1, in <module>
abs (-1)
TypeError: 'int' object is not callable
>>> abs = f
>>> abs(-1)
1
>>>
- 函數(shù)名也只是一個變量,跟普通變量沒什么區(qū)別,只是他指向了一個函數(shù),其他變量指向整數(shù)或者字符串等等。
*由于abs函數(shù)實際上是定義在import builtins模塊中的,所以要讓修改abs變量的指向在其它模塊也生效,要用import builtins; builtins.abs = 10。
傳入函數(shù)
- 一個函數(shù)就可以接收另一個函數(shù)作為參數(shù),這種函數(shù)就稱之為高階函數(shù)。
def add(a, b, abs):
return a + abs(b)
print(add(2, -3, abs))
==============================
5
- 編寫高階函數(shù),就是讓函數(shù)的參數(shù)能夠接收別的函數(shù)。
map/reduce
map函數(shù)
- map()函數(shù)接收兩個參數(shù),一個是函數(shù),一個是Iterable,map將傳入的函數(shù)依次作用到序列的每個元素,并把結(jié)果作為新的Iterator返回。
def f(a):
return a*a
r=[1, 2, 3, 4, 5, 6, 7, 8, 9]
r = map(f, r)
print(list(r))
============================
[1, 4, 9, 16, 25, 36, 49, 64, 81]
- map()傳入的第一個參數(shù)是f,即函數(shù)對象本身。由于結(jié)果r是一個Iterator,Iterator是惰性序列,因此通過list()函數(shù)讓它把整個序列都計算出來并返回一個list。
reduce函數(shù)
- reduce把一個函數(shù)作用在一個序列[x1, x2, x3, ...]上,這個函數(shù)必須接收兩個參數(shù),reduce把結(jié)果繼續(xù)和序列的下一個元素做累積計算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
- 如把序列[1, 2, 3, 4]變換成整數(shù)1234
import functools
def f(a, b):
return a*10 + b
print(functools.reduce(f,[1,2,3,4]))
=============================
1234
filter
- Python內(nèi)建的filter()函數(shù)用于過濾序列。
- 和map()類似,filter()也接收一個函數(shù)和一個序列。和map()不同的是,filter()把傳入的函數(shù)依次作用于每個元素,然后根據(jù)返回值是True還是False決定保留還是丟棄該元素。
- 如保留一個List中的奇數(shù)
def is_odd(x):
return x % 2 == 1
L1 = range(0, 5)
L2 = filter(is_odd, L1)
print(list(L2))
=========================
[1, 3]
sorted
- Python內(nèi)置的sorted()函數(shù)就可以對list進行排序
- sorted()函數(shù)也是一個高階函數(shù),它還可以接收一個key函數(shù)來實現(xiàn)自定義的排序,例如按絕對值大小排序:
L1 = [1, -2, 9, -3, 4]
L1 = sorted(L1, key=abs)
print(L1)
============================
[1, -2, -3, 4, 9]
- 對字符串排序,是按照ASCII的大小比較的
- 要進行反向排序,不必改動key函數(shù),可以傳入第三個參數(shù)reverse=True:
L2 = ["Jack", "Mike", "jason", "luke"]
L2 = sorted(L2, key=str.lower, reverse=True)
print(L2)
==============================
['Mike', 'luke', 'jason', 'Jack']
返回函數(shù)
- 高階函數(shù)除了可以接受函數(shù)作為參數(shù)外,還可以把函數(shù)作為結(jié)果值返回。
def lazy_sum(*args):
def cal_sum():
ax = 0
for n in args:
ax = n + ax
return ax
return cal_sum
f1 = lazy_sum(1, 2, 3, 4, 5, 6, 7, 8)
f2 = lazy_sum(1, 2, 3, 4, 5, 6, 7, 8)
print(f1())
print(f1 == f2)
print(f1() == f2())
函數(shù)lazy_sum返回的是一個內(nèi)部定義的cal_sum函數(shù),所以只有調(diào)用f()才會顯示結(jié)果。
當我們調(diào)用lazy_sum()時,每次調(diào)用都會返回一個新的函數(shù),即使是傳入相同的參數(shù)
即f1()和f2()的調(diào)用結(jié)果互不影響。
閉包
- 在上面的例子中,函數(shù)lazy_sum中又定義了函數(shù)cal_sum,內(nèi)部函數(shù)cal_sum可以引用外部函數(shù)lazy_sum的參數(shù)和局部變量,當lazy_sum返回函數(shù)cal_sum時,相關(guān)參數(shù)和變量都保存在返回的函數(shù)中,這種稱為“閉包(Closure)”的程序結(jié)構(gòu)擁有極大的威力。
- 當一個函數(shù)返回了一個函數(shù)后,其內(nèi)部的局部變量還被新函數(shù)引用
- 返回的函數(shù)并沒有立刻執(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()
print(f1())
print(f2())
print(f3())
=================================
9
9
9
可以看到上面的輸出全為9,而不是1,4,9,是因為在調(diào)用f1(),f2(),f3()時,i已經(jīng)變成了3
- 返回閉包時牢記的一點就是:返回函數(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))
return fs
=========================
1
4
9
匿名函數(shù)
- 當我們在傳入函數(shù)時,有些時候,不需要顯式地定義函數(shù),直接傳入匿名函數(shù)更方便。
L = list(map(lambda x: x*x, [1, 2, 3, 4, 5]))
print(L)
===================================
[1, 4, 9, 16, 25]
- 用匿名函數(shù)有個好處,因為函數(shù)沒有名字,不必擔(dān)心函數(shù)名沖突。此外,匿名函數(shù)也是一個函數(shù)對象,也可以把匿名函數(shù)賦值給一個變量,再利用變量來調(diào)用該函數(shù)
f = lambda x: x*x
print(f(5))
======================
25
- 也可以把匿名函數(shù)作為返回值返回
def get_sum(x, y):
return lambda: x+y
f = get_sum(1,2)
print(f())
裝飾器
- decorator就是一個返回函數(shù)的高階函數(shù)。
- 定義一個能打印日志的decorator:
def log(func):
def wrapper(*args, **kw):
print('call %s()' % func.__name__)
return func(*args, **kw)
return wrapper
- 借助Python的@語法,把decorator置于函數(shù)的定義
@log()
def now():
print(time.strftime("%y %m %d", time.localtime()))
- 調(diào)用now()函數(shù),不僅會運行now()函數(shù)本身,還會在運行now()函數(shù)前打印一行日志:
call now()
17 11 07
- 相當執(zhí)行了:
now = log(now)
- 可想而知,now的函數(shù)名發(fā)生了變化:
>>> now.__name__
'wrapper'
- 使用Python內(nèi)置的functools.wraps解決:
def log(*text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print("begin call")
if text:
print('%s %s' % (text[0], func.__name__))
else:
print('call %s()' % func.__name__)
func(*args, **kw)
print("end call")
return
return wrapper
return decorator
該函數(shù)經(jīng)過了一些修改
- decorator可以增強函數(shù)的功能,定義起來雖然有點復(fù)雜,但使用起來非常靈活和方便。
偏函數(shù)
- 偏函數(shù)是指通過設(shè)定參數(shù)的默認值,可以降低函數(shù)調(diào)用的難度。
- functools.partial可以幫助我們創(chuàng)建一個偏函數(shù)
import functools
int2 = functools.partial(int, base=2)
print(int2('100000'))
- 新的int2函數(shù),僅僅是把base參數(shù)重新設(shè)定默認值為2,但也可以在函數(shù)調(diào)用時傳入其他值
print(int2('100000', base = 8))
- 創(chuàng)建偏函數(shù)時,實際上可以接收函數(shù)對象、args和*kw這3個參數(shù),當傳入:
int2 = functools.partial(int, base=2)
相當于:
kw = { 'base': 2 }
int('10010', **kw)
``
當傳入:
max2 = functools.partial(max, 10)
實際上會把10作為*args的一部分自動加到左邊
相當于:
args = (10, 5, 6, 7)
max(*args)