閉包總結
一、閉包的本質
閉包指延伸了作用域的函數,其中包含函數定義體中引用,但是不是定義體中定義的非全局變量。
二、舉例理解概念
1、函數要求:avg函數,作用是計算不斷增長的系列值的均值;例如,整個歷史中某個商品的平均收盤價。每天都會增加新價格,因此平均值要考慮至目前為止所有的價格。
>>>avg(10) 10.0 >>>avg(20) 15.0 >>>avg(30) 20.0
2、實現方式1:使用類實現
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()
print(avg(10)) #10.0
print(avg(20)) #15.0
print(avg(30)) #20.0
函數說明:
1、定義一個類變量保存價格,avg = Averager() 類的實例化,生成實例對象時,創建self.series,是一個數組。
2、__call__ 魔法方法:使得 Averager類的 avg 實例對象變成了可調用對象。
3、每次調用 avg(arg) 時,類變量的列表都增加一個 arg 的元素,返回 列表總和的平均值。
3、實現方式2:使用高階函數實現(把函數作為參數傳入,這樣的函數稱為高階函數)
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.__code__.co_varnames) #('new_value', 'total')
print(avg.__code__.co_freevars) #('series',)
print(avg(10)) #10.0
print(avg(20)) #15.0
print(avg(30)) #20.0
print(avg.__closure__) #(<cell at 0x00000151B2C415E8: list object at 0x00000151B2DA7AC8>,)
print(avg.__closure__[0].cell_contents) #[10, 20, 30]
函數說明:
1. 調用 make_averager() 時,返回一個 averager 函數對象。每次調用 averager 時,它會把參數添加到系列值中,然后計算平均值。
2. series 是 make_averager 函數的局部變量
4、方法1和方法2的相通之處和不同之處
相通:
調用 Averager() 或 make_averager() 都得到一個可調用對象 avg,它會更新歷史值,然后計算當前均值。
我們只需要調用 avg(n) ,把n放入到系列值中,然后重新計算均值。
不同:
Averager 類的實例 avg 在 self.series 中存儲歷史值;
make_averager 函數在哪里尋找 series ?series 是 make_averager 函數的局部變量,因為那個函數的定義體中初始化了series, series = [] 。可是調用avg(10)時,make_averager 函數已經返回了,它的本地作用域也一去不復返了。
三、閉包和自由變量
1、閉包和自由變量:在實現方式2的基礎上
# 整個部分稱為閉包
series = []
def averager(new_value):
series.append(new_value) # series 稱為自由變量
total = sum(series)
return total/len(series)
說明:
averager 的閉包延伸到那個函數的作用域之外,包含自由變量的綁定。
2、func.__code__
print(avg.__code__.co_varnames) #('new_value', 'total') 將函數參數和局部變量以元組的形式返回
print(avg.__code__.co_freevars) #('series',) 返回內部函數中引用外部函數參數,即自由變量(通過函數閉包引用)
更詳細的說明參考:
https://blog.csdn.net/jpch89/article/details/86764245
3、func.__closure__
# 示例2中 print(avg.__closure__) #(<cell at 0x00000151B2C415E8: list object at 0x00000151B2DA7AC8>,) print(avg.__closure__[0].cell_contents) #[10, 20, 30] 或者遍歷得到: [cell.cell_contents for cell in func.__closure__]
說明:
1. Python 3 已將名為func_X 的函數屬性重命名為使用 __X__ 形式,從而在函數屬性名稱空間中為用戶定義的屬性釋放了這些名稱。
例如,func_closure,func_code,func_defaults,func_dict,func_doc,func_globals,func_name
分別重命名為__closure__,__code__,__defaults__,__dict__,__doc__,__globals__,__name__。
2. 其實閉包函數相對于普通函數會多出一個closure的屬性,里面定義了一個元組用于存放所有的cell對象,每個cell對象一一保存了這個閉包中所有的外部變量。
data model將__closure__定義為:None或包含該函數的自由變量綁定的單元格元組。
如果__closure__不是None,它將創建一個字典,將__code__.co_freevars中的每個單元格名稱映射到元組中的相應cell.cell_contents。
3. “Cell”對象用于實現由多個作用域引用的變量。
對于每個這樣的變量,一個“Cell”對象為了存儲該值而被創建;引用該值的每個堆棧框架的局部變量包含同樣使用該變量的對外部作用域的“Cell”引用。
訪問該值時,將使用“Cell”中包含的值而不是單元格對象本身。 這種對“Cell”對象的非關聯化的引用需要支持生成的字節碼;訪問時不會自動非關聯化這些內容。
“Cell”對象在其他地方可能不太有用。
引用自:https://docs.python.org/zh-cn/3.7/c-api/cell.html
4、總結
閉包是一種函數,它會保留定義函數時存在的自由變量的綁定,這樣調用函數時,雖然定義作用域不可用了,但是仍能使用那些綁定。
注意:只有嵌套在其他函數中的函數,才可能需要處理不在全局作用域中的外部變量。
四、nonlocal 聲明
1、背景:
實現方式2中 make_averager 函數的效率并不高;有缺陷。
2、缺陷說明舉例
>>> def make_averager():
count = 0
total = 0
def averager(new_value):
count += 1
total += new_value
return total / count
return averager
>>> avg = make_averager()
>>> avg(10)
Traceback (most recent call last):
File "<pyshell#5>", line 1, in <module>
avg(10)
File "<pyshell#3>", line 6, in averager
count += 1
UnboundLocalError: local variable 'count' referenced before assignment
>>> 未綁定的局部錯誤:賦值前引用了局部變量“count”
代碼說明:
當count 是數字,字符串,元組等不可變類型時,count += 1 語句的作用和 count = count+1 一樣。因此我們在averager的定義體中為count賦值了,這會把count變成局部變量。total變量同理。
在 實現方法2中沒有這個問題是因為沒有給 series 賦值,我們只是調用 series.append ,并把它傳給sum 和 len。也就是說我們利用了列表是可變對象這一事實。
但是對于不可變類型,只能讀取不可更新。如果嘗試重新綁定,其實會隱式創建局部變量 count。這樣count就不是自由變量了,因此不會保存在閉包中。
為了解決這個問題,Pyhon 引入了 nonlocal 聲明,它的作用是把變量標記為自由變量,即使在函數中為變量賦予了新值,也會變成自由變量。
如果為 nonlocal 聲明的變量賦予了新值,閉包中保存的綁定就會更新。
3、實現方式3:nonlocal 實現
def make_averager():
count = 0
total = 0
def averager(new_value):
nonlocal count,total
count += 1
total += new_value
return total / count
return averager
avg = make_averager()
print(avg(10)) # 10.0
print(avg(20)) # 15.0
print(avg(30)) # 20.0
浙公網安備 33010602011771號