閉包不好理解,所以先從示例說起与学。
假設我們需要計算平均值谱煤,這些值會從外層傳遞進來摊求,然后被保存在內(nèi)部。
(1) 非閉包方式實現(xiàn)
class Averager():
def __init__(self):
self.series = []
def __call__(self, new_value):
self.series.append(new_value)
total = sum(self.series)
return total / len(self.series)
avg = Averager()
logging.info('avg(10) -> %s', avg(10))
logging.info('avg(20) -> %s', avg(20))
logging.info('avg(30) -> %s', avg(30))
運行結果:
- 非閉包方式定義了一個類刘离,名為 Averager室叉。然后在初始化方法中為該類定義了一個數(shù)組 series,用于保存?zhèn)魅脒M來的數(shù)值硫惕。
- 接著使用
__call__
使得該類實例對象可以像調(diào)用普通函數(shù)那樣茧痕,以“對象名()”的形式被使用1。它接收一個參數(shù)作為需要計算的新數(shù)值恼除,內(nèi)部被保存在 series 數(shù)組中踪旷。
(2) 閉包方式實現(xiàn)
這個平均數(shù)計算方式可以采用函數(shù)式編程方式來實現(xiàn)。
def make_averager():
series = []
def averager(new_value):
series.append(new_value)
total = sum(series)
return total / len(series)
return averager
avg = make_averager()
logging.info('avg(10) -> %s', avg(10))
logging.info('avg(20) -> %s', avg(20))
logging.info('avg(30) -> %s', avg(30))
運行結果與上例相同。
- 我們定義了一個 make_averager 函數(shù)令野,其內(nèi)部又定義了一個名為 averager(new_value) 的函數(shù)舀患,里面是計算平均數(shù)的算法;
- 傳入的參數(shù)被保存在外層的 series 數(shù)組中气破;
- 最后返回這個內(nèi)部函數(shù)聊浅。
黃色區(qū)域表示產(chǎn)生閉包現(xiàn)象的代碼段。其中的 series 數(shù)組是自由變量(free variable)堵幽,不受內(nèi)部函數(shù) averager 的影響狗超,所以可以保存所有傳入的變量值。
內(nèi)部函數(shù) averager 的局部作用域內(nèi)的變量朴下,都會在函數(shù)被調(diào)用后失效努咐。
通過 __code__
屬性可以看到 avg 函數(shù)中的變量名稱。__code__
屬性是編譯后的函數(shù)定義體殴胧。
logging.info('avg.__code__.co_varnames -> %s', avg.__code__.co_varnames)
logging.info('avg.__code__.co_freevars -> %s', avg.__code__.co_freevars)
運行結果:
其中 co_varnames 表示局部變量渗稍;co_freevars 表示自由變量。這與我們之前所描述的閉包場景一致团滥。
閉包中的自由變量值保存在 avg 函數(shù)的 __closure__
屬性中竿屹。它是一組 cell 對象列表,每個 cell 對象與 co_freevars 列表中的名稱一一對應:
INFO - avg.__closure__ -> (<cell at 0x000002A8AF736D38: list object at 0x000002A8AF9C7DC8>,)
INFO - avg.__closure__[0].cell_contents -> [10, 20, 30]
運行結果:
closure 翻譯過來就是閉包灸姊。
通過閉包拱燃,我們可以保留住定義的自由變量的值。這樣函數(shù)調(diào)用后力惯,我們?nèi)匀豢梢允褂眠@些變量碗誉。
-
Python
__call__()
方法(詳解版). - Luciano Ramalho (作者),安道父晶,吳珂 (譯者).流暢的Python[M].人民郵電出版社哮缺,2017:312-315.