閉包是一種特殊的函數,閉包通常由兩個函數構成,分別爲內函數和外函數,內函數會引用外函數傳入的參數,而外函數返回的結果是內函數自己。python
好比:c#
def func_out(a):#外函數 b=10#a,b都是外函數的變量 def func_in(c):#c是內函數的變量 return a+b+c#內函數調用外函數的變量 return func_in #返回內函數自己 >>>m=func_out(1)#此時外函數傳入a=1,返回的是m本質上就是func_in函數對象 >>>m(3)#調用func_in()函數,傳入func_in函數的參數c=3 14
能夠看到,閉包一個很大的特色就是內函數在調用外函數的臨時變量。在python中,一個函數內部的變量,若沒有特別聲明爲全局變量,都爲該函數局部域下的臨時變量,當函數執行完,內部的局部變量也會回收釋放掉。而上面這個例子的a和b都還沒被釋放,能夠調用,由於外函數還沒有執行完,內函數也屬於外函數之中。閉包
能夠這麼理解:app
在函數內部能夠直接調用全局變量,如:函數
a=10 #外部全局變量 def func(b): return a+b >>>11
所以,在閉包中,外函數的域若是爲A,內函數的域爲B,那麼B是屬於A的,或者說A是B的相對全局域,那麼內函數中天然能夠調用外函數的變量。code
def func_out(a): b=10 def func_in(): return a+b return func_in >>>m=func_out(1) >>>type(m) <class 'function'> >>>m(1) 12
閉包返回的是內函數,能夠看到,調用一個閉包,最後調用的時候都是在內函數實現,而外函數更可能是進行了部分變量或其它相關信息的保存和初始化。以後每次調用閉包或者內函數時,這些通用的變量就不用再傳進去了。這一點很像一個單函數的類,好比:對象
class A: def __init__(self,a,b): self.a=a self.b=b def func(self,c): return self.a+self.b >>>m=A(1,10) >>>m.func(1) 11
上述例子中類所實現的功能就和以前的閉包例子同樣。所以不少單方法類能夠轉換爲閉包來實現。input
閉包很大的一個應用就是用做裝飾器。如咱們想給某些須要進行計算的函數進行計時,對某些函數的輸入參數進行強制類型檢驗,這類場景經過裝飾器就能夠很是優美的實現,而非在每一個函數下重複複製相似的代碼。it
def timer_func(func): ''' 用於對調用func函數時進行計時 :param func: 函數對象做爲外函數的入參 :return: 內函數 ''' #內函數 def wrapper(*args,**kwargs):#經過*args,**kwargs來傳入函數的全部參數 t1=time.time() r=func(*args,**kwargs)#內函數調用外函數的參數,該參數是一個函數對象 t2=time.time() cost=t2-t1 print('time cost %s'%cost) return r#返回了func(*args,**kwargs)的結果 return wrapper def func(n): while n>0: n=n-1 return n >>>a=timer_func(func)#返回的是內部的wrapper函數對象 >>>a(100000)##10000會就是*args傳入func中。 time cost 0.009999752044677734 0
能夠看到,整個裝飾函數都只是增長了一個計時功能,而須要被計時的函數則該怎麼調用怎麼調用。因此內部函數通常返回的都是func(args,*kwargs),即被裝飾函數的執行結果。這種裝飾能夠直接經過語法糖@實現:io
@timer_func def func(n): while n>0: n=n-1 return n >>>func(9999999) time cost 0.7122969627380371
在撰寫某些函數時,咱們時常想指定輸入參數的類型。而在python中,並無像c語言等有類型檢查等功能,由於python是一門動態類型語言。一種方式是經過註解的方式「規定」類型,如:
def func(a:int,b:str)->list: res=[a**2,b] return res #這裏的限制僅是一種規約和提醒,並不會報錯,但在IDE中會進行提示。 >>>print(func(1,'2')) [1, '2'] >>>print(func(2,3))#並不會報錯 [4, 3]
若是要對輸入參數的類型進行強制檢查,則可使用裝飾器,不然對每一個函數都進行if else等判斷來檢查類型,代碼將很是冗長,不易突出核心代碼。
裏面會涉及到inspect庫中signature相關的操做:
import inspect def func(a:int,b:str)->list: res=[a**2,b] return res >>>sig=inspect.signature(func)#提取函數的簽名,返回一個Signature對象 >>>sig <Signature (a: int, b: str) -> list> >>>parameters=sig.parameters#獲取參數的有序字典 >>>parameters mappingproxy(OrderedDict([('a', <Parameter "a: int">), ('b', <Parameter "b: str">)])) >>>arg_names=tuple(parameters.keys())#獲取全部的參數名稱 >>>arg_names ('a', 'b')
所以,若是經過構建裝飾器對函數的參數進行類型檢查,則裝飾器內部自動提取函數的各個參數以及期待的類型,而後進行循環檢驗便可:
def para_check(func):#外函數,傳入的參數是待檢驗函數對象自己 sig=inspect.signature(func)#獲取函數參數簽名 parameters=sig.parameters#獲取參數的有序字典 arg_names=tuple(parameters.keys())#獲取參數的名稱 def wrapper(*args,**kwargs):#內函數 check_list=[]#待檢驗的參數對 for i,val in enumerate(args):#檢驗全部的位置參數 arg_name=arg_names[i] anno=parameters[arg_name].annotation#該參數期待的類型 check_list.append((arg_name,anno,val)) for arg_name,val in kwargs.items():#檢驗全部的關鍵字參數 anno=parameters[arg_name].annotation check_list.append((arg_name,anno,val)) for check_arg in check_list:#逐個參數檢驗 if not isinstance(check_arg[2],check_arg[1]): raise TypeError('the input %s expect type %s,but got %s'%(check_arg[0],check_arg[1],type(check_arg[2]))) return func(*args,**kwargs) return wrapper @para_check def test(x: int, y: int): return x + y >>>print(test(1,2)) 3 >>>print(test(1,'3')) TypeError: the input y expect type <class 'int'>,but got <class 'str'>