函數閉包入門

函數閉包入門

什麼是閉包

閉包是一種特殊的函數,閉包通常由兩個函數構成,分別爲內函數和外函數,內函數會引用外函數傳入的參數,而外函數返回的結果是內函數自己。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

幾點理解

  1. 閉包返回的是一個函數對象
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'>
相關文章
相關標籤/搜索