閉包函數
內部函數包含對外部作用域而非全局作用域名字的引用,該內部函數稱為閉包函數。
由於有了作用域的關系,我們就不能拿到函數內部的變量和函數了。如果我們就是想拿怎么辦呢?返回呀!
我們都知道函數內的變量我們要想在函數外部用,可以直接返回這個變量,那么如果我們想在函數外部調用函數內部的函數呢?
是不是直接就把這個函數的名字返回就好了?
閉包函數最常用的用法
def func():
name = 'diege'
def inner():
print(name)
return inner
f = func()
f()
簡單剖析一下上面的代碼流程
第一步:定義func變量開辟存放變量的內存空間和名字
第二步:調用func並賦值給f變量
第三步:創建name變量和值
第四步:返回值是func中的內部函數inner的引用
第五步:調用f函數(此時 f 擁有name變量和內部函數inner)
第六步:進入inner函數
第七步:print
例子2 可以嘗試調試以下代碼
def wrapper():
money = 1000
def func():
name = 'diege'
def inner():
print(name,money)
return inner
return func
f = wrapper()
i = f()
i()
由於閉包這個概念比較難以理解,尤其是初學者來說,相對難以掌握,所以我們通過示例去理解學習閉包。
給大家提個需求,然后用函數去實現:完成一個計算不斷增加的系列值的平均值的需求。
例如:整個歷史中的某個商品的平均收盤價。什么叫平局收盤價呢?就是從這個商品一出現開始,每天記錄當天價格,然后計算他的平均值:平均值要考慮直至目前為止所有的價格。
比如大眾推出了一款新車:小白轎車。
第一天價格為:100000元,平均收盤價:100000元
第二天價格為:110000元,平均收盤價:(100000 + 110000)/2 元
第三天價格為:120000元,平均收盤價:(100000 + 110000 + 120000)/3 元
........
series = []
def make_averager(new_value):
series.append(new_value)
total = sum(series)
return total / len(series)
print(make_averager(100000))
print(make_averager(110000))
print(make_averager(120000))
從上面的例子可以看出,基本上完成了我們的要求,但是這個代碼相對來說是不安全的,因為你的這個series列表是一個全局變量,只要是全局作用域的任何地方,都可能對這個列表進行改變。
series = []
def make_averager(new_value):
series.append(new_value)
total = sum(series)
return total / len(series)
print(make_averager(100000))
print(make_averager(110000))
series.append(666) # 如果對數據進行相應改變,那么你的平均收盤價就會出現很大的問題。
print(make_averager(120000))
那么怎么辦呢?有人說,你把他放在函數中不就行了,這樣不就是局部變量了么?數據不就相對安全了么?
def make_averager(new_value):
series = []
series.append(new_value)
total = sum(series)
return total / len(series)
print(make_averager(100000)) # 100000.0
print(make_averager(110000)) # 110000.0
print(make_averager(120000)) # 120000.0
這樣計算的結果是不正確的,那是因為執行函數,會開啟一個臨時的名稱空間,隨着函數的結束而消失,所以你每次執行函數的時候,都是重新創建這個列表,那么這怎么做呢?這種情況下,就需要用到我們講的閉包了,我們用閉包的思想改一下這個代碼。
def make_averager():
series = []
def averager(new_value):
series.append(new_value)
total = sum(series)
return total/len(series)
return averager
avg = make_averager()
print(avg(100000))
print(avg(110000))
print(avg(120000))
大家仔細看一下這個代碼,我是在函數中嵌套了一個函數。那么avg 這個變量接收的實際是averager函數名,也就是其對應的內存地址,我執行了三次avg 也就是執行了三次averager這個函數。那么此時你們有什么問題?
肯定有人就會問,那么我的make_averager這個函數只是執行了一次,為什么series這個列表沒有消失?反而還可以被調用三次呢?這個就是最關鍵的地方,也是閉包的精華所在。
一般情況下,在我們認知當中,如果一個函數結束,函數的內部所有東西都會釋放掉,還給內存,局部變量都會消失。但是閉包是一種特殊情況,如果外函數在結束的時候發現有自己的臨時變量將來會在內部函數中用到,就把這個臨時變量綁定給了內部函數,然后自己再結束。
上面被紅色方框框起來的區域就是閉包,被藍色圈起來的那個變量應該是make_averager()函數的局部變量,它應該是隨着make_averager()函數的執行結束之后而消失。但是他沒有,是因為此區域形成了閉包,series變量就變成了一個叫自由變量的東西,averager函數的作用域會延伸到包含自由變量series的綁定。也就是說,每次我調用avg對應的averager函數時,都可以引用到這個自由變量series,這個就是閉包。
還有一點需要注意:使用閉包的過程中,一旦外函數被調用一次返回了內函數的引用,雖然每次調用內函數,是開啟一個函數執行過后消亡,但是閉包變量實際上只有一份,每次開啟內函數都在使用同一份閉包變量。
請看以下例子
def outer(x):
def inner(y):
nonlocal x
x+=y
return x
return inner
a = outer(5)
print(a(1))
print(a(3))
開始執行108行
nonlocal關鍵字聲明使用外層(非全局)變量
此時返回值為6
開始執行109行后 發現x為108行執行后的返回值
此時這個自由變量為9了
那么我們再加個print(a(10))應該返回值就是14啦
由此可見,從上面的例子就能說明每次調用inner時候,使用的閉包變量x實際上是同一個。
閉包的作用:保存局部信息不被銷毀,保證數據的安全性。
閉包的應用:
-
可以保存一些非全局變量但是不易被銷毀、改變的數據。
-
裝飾器。