Python高階函式(map/reduce、filter、sorted)、返回函式、裝飾器、偏函式
高階函式:
首先我們先舉個例子,以Python內建函式abs()為例:
>>> abs(-10)
10
>>> abs
<built-in function abs>
我們可以看出abs(-10)
是函式呼叫,而abs
是函式本身。
執行截圖如下:
再舉一個例子:
>>> x = abs(-10)
>>> x
10
>>> f = abs
>>> f
<built-in function abs>
abs(-10)函式呼叫的結果可以賦值給變數,abs函式本身也可以賦給變數(這樣變數指向了這個函式,相當於給函式起了一個別名)。
執行截圖如下:
此時我們如果用變數名代替函式來進行函式呼叫,如:
>>> f = abs
>>> f(-10)
10
>>> abs(-10)
10
可以看到呼叫變數f(-10)和呼叫函式abs(-10)完全相同。
執行截圖如下:
從上面的例子,我們得到結論:
函式名就是一個指向函式本身的變數,f與abs不同的是abs是預設指向取絕對值函式的變數,而f是我們人為指向取絕對值函式的變數。
高階函式的定義:
一個函式如果它的引數中有些引數可以接收另一個函式作為引數,這種函式就稱之為高階函式。
如:
def add(x, y, f): return f(x) + f(y) x = -5 y = 6 f = abs print(add(x,y,f))
執行截圖如下:
map()函式:
map()
函式接收兩個引數,一個是函式,一個是Iterable
,map
將傳入的函式依次作用到序列的每個元素,並把結果作為新的Iterator
返回(Python3中返回Iterator迭代器,Python2中返回列表(list))。
如:
我們有一個函式f(x)=x*x,我們想讓一個list的所有元素依次帶入這個函式進行運算並得到結果,則:
def f(x):
return x*x
r=map(f,[1,2,3,4,5,6,7,8,9])
print(list(r))
list() 方法用於將元組轉換為列表。這裡可以通過list()
函式讓它把整個序列都計算出來並返回一個list。
執行截圖如下:
注意:
迭代器的一個優點就是它不要求你事先準備好整個迭代過程中所有的元素。
迭代器僅僅在迭代至某個元素時才計算該元素,而在這之前或之後,元素可以不存在或者被銷燬。這個特點被稱為延遲計算或惰性求值(Lazy evaluation)。
具有惰性計算特點的序列稱為惰性序列。Python的iterator是一個惰性序列,意思是表示式和變數繫結後不會立即進行求值,而是當你用到其中某些元素的時候才去求某元素對的值。 惰性是指,你不主動去遍歷它,就不會計算其中元素的值。
reduce()函式:
reduce() 函式會對引數序列中元素進行累積。用傳給 reduce 中的函式 function(有兩個引數)先對集合中的第 1、2 個元素進行操作,得到的結果再與第三個資料用 function 函式運算,依次類推直到運算完集合中的最後一個元素,最後返回函式計算結果。
如,reduce實際等同於:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
舉例:
from functools import reduce
def add(x,y):
return x+y
sum=reduce(add,[1,3,5,7,9])
print(sum)
執行截圖如下:
再比如,配合使用map()函式,我們就可以把一個由數字字元組成的字串轉換成數字。
from functools import reduce
def fn(x,y):
return x*10+y
def char2num(s):
digits={'0':0,'1':1,'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9}
return digits[s]
result=reduce(fn,map(char2num,'13579'))
print(result)
執行截圖如下:
上面的程式還可以用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 strtoint(s):
return reduce(lambda x,y:x*10+y,map(char2num,s))
result=strtoint('13579')
print(result)
lambda x,y:x*10+y是一個匿名函式,表示函式傳入兩個引數x和y,計算x*10+y並將結果返回。
執行截圖如下:
filter()函式:
filter()
函式用於過濾序列。filter()
函式接收一個函式和一個序列。filter()
把傳入的函式依次作用於每個元素,然後根據返回值是True
還是False
決定保留還是丟棄該元素。
如,在一個list中,刪掉偶數,只保留奇數:
def odd(n):
return n%2==1
print(list(filter(odd,[1,2,4,5,6,9,10,15])))
執行截圖如下:
注意:filter()
函式返回的是一個Iterator
,和map()函式類似,也是一個惰性序列,所以要強迫filter()
完成計算結果,需要用list()
函式獲得所有結果並返回list。
舉例:用filter求素數
我們使用埃拉托色尼篩選法。
首先,列出從2
開始的所有自然數,構造一個序列:
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...
取序列的第一個數2
,它一定是素數,然後用2
把序列的2
的倍數篩掉:
3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...
取新序列的第一個數3
,它一定是素數,然後用3
把序列的3
的倍數篩掉:
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...
依次類推,不斷篩下去,就可以得到所有的素數。
如,我們先構造一個從3開始只有奇數的序列(除2以外的偶數必不是素數):
def odd()://構造一個無限的奇數序列
n=1
while True:
n=n+2
yield n
def _not_divisible(n)://篩選出素數的函式
return lambda x:x%n>0
def primes()://定義一個生成器,不斷生成下一個素數
yield 2
it=odd()//初始惰性序列
while True:
n=next(it)//返回序列的第一個數
yield n
it=filter(_not_divisible(n),it)//用當前的n將序列中的數篩了一遍,形成新序列
for n in primes()://列印1000以內素數
if n<1000:
print(n)
else:
break
執行截圖如下:
sorted()函式:
在python中對list進行排序有兩種方法:
用List的成員函式sort進行排序,在原list中進行排序,不返回副本;
用built-in函式sorted進行排序(從2.4開始),返回副本,原始輸入不變。
python3中sorted取消了對cmp的支援,格式:
sorted(iterable,key=None,reverse=False)
key的作用原理:key指定一個接收一個引數的函式,這個函式用於從每個元素中提取一個用於比較的關鍵字。預設值為None ;
reverse是一個布林值。如果設定為True,列表元素將被倒序排列,預設為False。
sort()與sorted函式舉例:
L=[36,5,-12,9,-21]
L.sort()
print(L)
S=[40,5,-12,9,-21]
print(sorted(S))
執行截圖如下:
sorted()
函式也是一個高階函式,它還可以接收一個key
函式來實現自定義的排序。key指定的函式將作用於list的每一個元素上,並根據key函式返回的結果進行排序。
如按絕對值大小排序:
S=[40,5,-12,9,-21]
print(sorted(S,key=abs))
執行截圖如下:
預設情況下,如果用sorted()或sort()對字串排序,是按照ASCII的大小比較的,由於'Z' < 'a'
,結果,大寫字母Z
會排在小寫字母a
的前面。
如:
L=['ABC','zDA','bef','Zec','Mnn']
L.sort()
print(L)
S=['ABC','zDA','bef','Zec','Mnn','abc']
print(sorted(S))
執行截圖如下:
如果上面的排序我們想忽略字母的大小寫,那麼只要用一個key函式把字串對映為忽略大小寫排序即可。
忽略大小寫來比較兩個字串,實際上就是先把字串都變成大寫(或者都變成小寫),再比較。我們還可以設定reverse=True來實現倒序排序。
如:
S=['ABC','zDA','bef','Zec','Mnn','abc']
print(sorted(S,key=str.lower,reverse=True))
執行截圖如下:
返回函式:
函式不僅可以作為函式引數,還可以作為另一個函式的返回結果。
如果在一個內部函式裡對外部作用域(但不是全域性作用域)的變數進行引用,內部函式稱為閉包(closure)。
對於高階函式,除了可以接受函式作為引數外,還可以把函式作為結果值返回。
如:
def pro1(c,f):
def pro2():
return f(c)
return pro2
a=pro1(-3,abs)
print(a)
執行截圖如下:
出現上面的結果是因為當我們呼叫pro1()
時,返回的並不是求和結果,而是求和函式。
如何才能計算函式結果呢?
def pro1(c,f):
def pro2():
return f(c)
return pro2
a=pro1(-3,abs)
print(a())
這樣才真正呼叫了函式,計算出結果。
執行截圖如下:
在上面的例子中,我們在函式pro1
中又定義了函式pro2
,並且,pro2可以引用外部函式pro1
的引數和區域性變數,當pro1
返回函式pro2
時,相關引數和變數都儲存在返回的函式中,這種結構就稱為“閉包(Closure)”。
注意:
當我們呼叫pro1()時,每一次呼叫都會返回一個新的函式,即使傳入的引數相同。
如:
請再注意一點,當我們呼叫lazy_sum()
時,每次呼叫都會返回一個新的函式,即使傳入相同的引數:
def pro1(c,f):
def pro2():
return f(c)
return pro2
a=pro1(-3,abs)
b=pro1(-3,abs)
print(a==b)
執行截圖如下:
要注意的是,返回的函式並沒有立刻執行,而是直到呼叫了a()
才執行。由於這個特性,返回閉包時要牢記一點:返回函式不要引用任何迴圈變數,或者後續會發生變化的變數。
我們舉個反例:
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,原因是返回的函式引用了變數i
,但它並非立刻執行。等到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)) # f(i)立刻被執行,因此i的當前值被傳入f()
return fs
f1, f2, f3 = count()
print(f1())
print(f2())
print(f3())
這次的結果是1,4,9。
執行截圖如下:
裝飾器:
裝飾器本質上是一個返回函式的高階函式。它可以讓其他函式在不需要做任何程式碼變動的前提下增加額外功能。
如,我們有下面這樣一個函式:
def now():
print('2018-9-15')
f=now
f()
我們知道函式物件都有一個__name__
屬性,儲存了函式的名字,於是我們將函式名字也打印出來:
def now():
print('2018-9-15')
f=now
f()
print(now.__name__)
print(f.__name__)
執行截圖如下:
假如我們現在想增強now()
函式的功能,如,在函式呼叫前後自動列印日誌,但又不希望修改now()
函式的定義,這種在程式碼執行期間動態增加功能的方式,就叫做“裝飾器”(Decorator)。
我們可以定義如下的一個裝飾器:
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
觀察上面的log
函式,因為它是一個decorator,所以接受一個函式作為引數,並返回一個函式。
我們藉助Python的@語法,把decorator置於函式的定義處。把@log
放到now()
函式的定義處,相當於執行了語句:now = log(now)。
由於log()
是一個decorator,返回一個函式,所以,原來的now()
函式仍然存在,只是現在同名的now
變數指向了新的函式,於是呼叫now()
將執行新函式,即在log()
函式中返回的wrapper()
函式。
wrapper()
函式的引數定義是(*args, **kw)
,因此,wrapper()
函式可以接受任意引數的呼叫。在wrapper()
函式內,首先列印日誌,再緊接著呼叫原始函式。
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
@log
def now():
print('2018-9-15')
f=now
f()
print(now.__name__)
print(f.__name__)
這個時候我們執行這段程式碼,不僅會執行now()
函式本身,還會在執行now()
函式前列印一行日誌。
經過decorator裝飾之後的函式,它們的__name__
屬性已經從原來的'now'
變成了'wrapper'。
因為返回的那個wrapper()
函式名字是'wrapper'
,所以,我們需要把原始函式的__name__
等屬性複製到wrapper()
函式中,否則,有些依賴函式簽名的程式碼執行就會出錯。
我們不需要編寫wrapper.__name__ = func.__name__
這樣的程式碼,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 now():
print('2018-9-15')
f = now
f()
print(now.__name__)
print(f.__name__)
執行截圖如下:
偏函式:
Python的functools
模組提供了很多有用的功能,其中一個就是偏函式(Partial function)。要注意,這裡的偏函式和數學意義上的偏函式不一樣。
偏函式是Python2.5版本以後引進來的。屬於函數語言程式設計的一部分,使用偏函式可以通過有效地“凍結”那些預先確定的引數,來快取函式引數,然後在執行時,當獲得需要的剩餘引數後,可以將他們解凍,傳遞到最終的引數中,從而使用最終確定的所有引數去呼叫函式。
如:
def add(a, b):
return a + b
print(add(3, 5))
print(add(4, 7))
這是我們正常呼叫函式的情況,那麼如果我們知道一個已知的引數a= 100,我們如何利用偏函式呢?
import functools
def add(a, b):
return a + b
puls = functools.partial(add,100)
print(puls(9))
執行截圖如下:
偏函式表達的意思就是,在函式add呼叫時,我們已經知道了其中的一個引數,我們可以通過這個引數,重新繫結一個函式,即functools.partial(add, 100),然後呼叫puls即可。