Python學習筆記五函數式編程(二)
參考教程:廖雪峰官網https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000
一、返回函數
高階函數除了可以接受函數作為參數之外,還可以把函數作為返回值。
通常我們也可以通過以下方式求和:
def calc_sum(*args): sum=0 for n in args: sum=sum+n return sum
但如果有一種情況 ,不需要立刻得出求和結果,而是在後續的代碼中根據需要再計算,這種情況不返回求和的結果,而是返回求和的函數:
def lazy_sum(*args):def funcsum(): sum=0 for n in args: sum=sum+n return sum return funcsum x=lazy_sum(1,3,5) #返回值賦給x,x是一個代入(2,3,5)元組局部變量的函數funcsum() #通過輸出可以看出x是一個函數 print(x) #需要調用x()時候才輸出求和結果 print(x())
對於上例,調用lazy_sum()時候傳入的參數(1,3,5)成為了新創建的funcsum()函數的內部變量,這樣的程序結構也稱為“閉包(Closure)”。
需要註意,即便傳入同樣的參數,返回的函數也是不同的:
x1=lazy_sum(1,3,5) x2=lazy_sum(1,3,5) print(x1==x2) #False
需要註意的是,返回函數並不立即執行,直到需要調用它的時候才執行,根據執行的時候各個參數和變量的值輸出結果,看下面的例子:
def count(): fs = [] for i in range(1, 4): def f(): return i*i fs.append(f) return fs #返回一個函數列表 f1, f2, f3= count() fx=count() print(f1()) print(f2()) print(f3()) print(fx[0]()) print(fx[1]()) print(fx[2]())
以上輸出全為9,是因為在生成(返回)f1,f2,f3和函數列表fx的時候,它們內部的變量是i,而在執行的時候i已經變成了3。所以在使用閉包時需要牢記:返回函數不要引用任何循環變量,或者後續會發生變化的變量。
如果一定要使用循環,那只有再創建一個函數,把循環變量綁定到其參數上,這樣的綁定在循環過程中就實現了,可以保證循環後也不變:
def mycount(): def f(j): def g(): return j*j return g fs=[] for i in range(1,4): #此時i在循環中已經把值傳遞給g函數的參數j了 fs.append(f(i)) return fs myf=mycount() print(myf[0]()) print(myf[1]()) print(myf[2]())
練習:
#利用閉包返回一個計數器函數,每次調用它返回遞增整數 def createCounter(): #註意這裏使用的變量是一個列表,這是避免一個報錯bug mycounter=[0] def counter(): mycounter[0]=mycounter[0]+1 return mycounter[0] return counter ‘‘‘ python3中也可以用nonlocal先聲明mycounter 這樣就可以避免報錯 def createCounter(): mycounter=0 def counter(): nonlocal mycounter mycounter=mycounter+1 return mycounter return counter ‘‘‘ tester=createCounter() while input()!=‘3‘: print(tester()) # 測試: counterA = createCounter() print(counterA(), counterA(), counterA(), counterA(), counterA()) # 1 2 3 4 5 counterB = createCounter() if [counterB(), counterB(), counterB(), counterB()] == [1, 2, 3, 4]: print(‘測試通過!‘) else: print(‘測試失敗!‘)
二、匿名函數
在Python中有時候不需要顯示定義函數,這樣也可以避免給函數起名的煩惱。
#關鍵字lambda就表示匿名函數,冒號前面的x是函數的參數 #返回值是x*x f=lambda x:x*x f(5) #返回25
上面代碼中的lambda表達式等同於下面代碼:
def f(x): return x*x
一個lambda表達式在map()中的應用:
la=list(map(lambda x:x**2+1,[1,2,3,4,5])) print(la) #輸出[2,5,10,17,26]
練習:
#請用匿名函數改造下面的代碼: def is_odd(n): return n % 2 == 1 L = list(filter(is_odd, range(1, 20))) L1=list(filter(lambda x:x%2==1,range(1,20))) print(L) print(L1)
三、裝飾器
某些情況下,我們需要增強若幹個函數的功能,比如打印日誌,但又不希望改動函數內部的代碼,則有一種在代碼運行期間動態增加功能的方式,稱之為“裝飾器”(Decorator)
例如,定義一個能打印日誌的裝飾器:
def log(func): def wrapper(*args,**kw): print(‘call %s‘ % func.__name__) return func(*args,**kw) return wrapper @log def add(a,b): return a+b print(add(3,6))
輸出結果如下:
call add
9
註意到通過‘@‘符號在函數定義前增加了語句,即給該函數增加了一個裝飾器功能。相當於執行了語句:now=log(add)。log(add)返回的是一個函數,該函數的功能不僅會運行函數本身,還運行了裝飾器的代碼。但這裏now變量指向了新的函數,即wrapper。
如果要在裝飾器中本身需要傳入參數,則需要編寫一個返回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(‘haha‘)#註意這裏參數的用法 def add(a,b): return a+b print(add(3,9)) print(add.__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 add(a,b): return a+b print(add(3,9)) print(add.__name__)
練習:
#請設計一個decorator,它可作用於任何函數上,並打印該函數的執行時間: #-*- coding: utf-8 -*- import time, functools def metric(fn): @functools.wraps(fn) #保持原始函數名 def timedeco(*args,**kws): start_time=time.time() print(‘START TIME:‘+time.asctime(time.localtime(start_time))) x=fn(*args,**kws) end_time=time.time() print(‘END TIME:‘+time.asctime(time.localtime(end_time))) print(‘%s executed in %s ms‘ % (fn.__name__, str(end_time-start_time))) return x return timedeco # 測試 @metric def fast(x, y): time.sleep(1.0012) return x + y; @metric def slow(x, y, z): time.sleep(1.1234) return x * y * z; f = fast(11, 22) print(f) print(‘\n\n\n‘) s = slow(11, 22, 33) print(s) if f != 33: print(‘測試失敗!‘) elif s != 7986: print(‘測試失敗!‘)
Python學習筆記五函數式編程(二)