Python基礎(10):返回函式,閉包以及裝飾器之間那些糾纏不清的關係
阿新 • • 發佈:2018-12-04
一:返回函式
定義:函式可以作為另一個函式的返回值。
理論來源:函式可以巢狀定義。
def fun1(li):
def fun2():
return sum(li)
return fun2
呼叫fun1函式,返回的是一個一個fun2例項。
獲取最終結果,需要執行:
test()
二:閉包
定義:在一個內部函式裡,對外在作用域(不是全域性作用域)的變數進行引用,這種程式結構被稱為閉包。
舉例:上例中的fun2是一個內部函式,它引用了li這個外部變數,li的作用域僅在fun1中,所以fun2,就是一個閉包。
閉包和返回函式的關係:閉包以返回函式的形式實現。
注意:閉包,是函式體和引用環境的整體。
警告:閉包中儘量不要引用迴圈變數,或後期會發生變化的變數。
例項:
def fun3():
f=[]
for i in range(1,4): #迴圈遍歷1,2,3
def fun4():
return i**2 #返回i的二次冪
f.append(fun4) #將結果追加到列表f中
return f
測試程式碼如下:
for i in fun3():
print(i())
原以為會返回一個[1,4,9]的列表,但是結果為[9,9,9]
這是因為,其實f.append(fun4)這行程式碼,不是將結果追加到了列表中,而是將fun4函式追加到列表中。
最終的列表f,每一個元素,都是一個閉包。
閉包是函式和執行環境的結合,在這裡,函式是i**2,而環境,就是i。當三個元素被追加完時,i已經變成了3。
所以,列表f的每一個元素都是3**2,也就是9
解決這類問題的方向是:增加一層隔離環境。
通過上面的例子我們明白,結果出現偏差的主要原因是,執行環境發生變化(i發生了變化)。那麼,可以這樣解決:
def fun3(): f=[] def fun4(j): def fun5(): return j ** 2 # 返回i的二次冪 return fun5 #返回一個閉包,函式為j**2,執行環境為j for i in range(1,4): #迴圈遍歷1,2,3 f.append(fun4(i)) #追加元素到f,每一個元素為一個閉包,在閉包內,變數恆為i return f
三:裝飾器
定義:在程式碼執行期間,動態增加功能的方式,成為裝飾器。
本質:裝飾器本質上是一個返回函式的高階函式。
與閉包的關係:是閉包的一種應用場景。
定義裝飾器的兩種方法:
1:利用閉包原理
雙層巢狀:定義一個裝飾器,接收函式作為引數,返回一個warpper閉包,在warpper內部,定義在函式執行前或後進行的操作。
例項:
import time
def log(fun): #定義一個裝飾器,接收函式fun為引數
def wrapper(*args,**kwargs): #內部函式wrapper,定義具體的裝飾操作
print('excute %s'%fun.__name__) #在函式執行前,列印excute 函式名
return fun(*args,**kwargs) #執行函式
return wrapper
@log #用@為函式新增裝飾器,相當於abc=log(abc)
def abc(): #abc=log(abc),執行wrapper閉包,首先列印excute abc
time.sleep(1) #執行函式本身,等待一秒
print('my name is abc') #執行列印方法
if __name__=='__main__':
abc()
三層巢狀:適用於裝飾器本身需要傳入引數。
def log(text): #定義一個裝飾器,接收普通引數text
def decorator(fun): #定義一個高階函式,接收函式為引數
def wrapper(*args,**kwargs): #內部函式wrapper,定義具體的裝飾操作
print('%s %s'%(text,fun.__name__)) #在函式執行前,列印text 函式名
return fun(*args,**kwargs) #執行函式
return wrapper #返回wrapper閉包
return decorator #返回decorator閉包
@log('ready,go!') #用@為函式新增裝飾器,並攜帶引數,相當於abc=log('ready go!')(abc)
def abc(): #abc=log('ready go!')(abc),執行decorator閉包,再執行wrapper閉包,首先列印ready go! abc
time.sleep(1) #執行函式本身,等待一秒
print('my name is abc') #執行列印方法
2:類裝飾器
class log(object):
def __init__(self,fun): #例項化需要接收一個函式
self.fun=fun #將函式賦給類的fun屬性,相當於接收一個函式作為引數
def __call__(self, *args, **kwargs): #能像方法一樣被呼叫
print('excute %s'%self.fun.__name__)#在函式執行前列印
return self.fun() #執行函式
@log #abc=log(abc),將abc作為log類的一個例項
def abc(): #abc已經變成一個Log物件,呼叫__call__方法,先列印excute abc
time.sleep(1) #睡眠
print('my name is abc') #列印my name is abc