轉(zhuǎn)載自https://serholiu.com/python-closures
了解閉包前蜗侈,先了解一下變量作用域
看個(gè)例子:
def f1(a):
print(a)
print(b)
f1(2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in f1
NameError: global name 'b' is not defined
上面的例子出錯(cuò)并不奇怪富拗,如果我們先給b復(fù)制跛锌,然后調(diào)用f1函數(shù)端辱,就不會(huì)出錯(cuò)了
b = 3
f1(3)
輸出: 3
3
再看一例:
b = 5
def f2(a):
print(a)
print(b)
b = 6
f2(2)
3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in f2
UnboundLocalError: local variable 'b' referenced before assignment
這不是缺陷次企,而是設(shè)計(jì)選擇醋火,python不要求聲明變量悠汽,但是假定在函數(shù)定義體重賦值的變量是局部變量。如果在函數(shù)中賦值時(shí)想讓解釋器把 b 當(dāng)成全局變量芥驳,要使用 global 聲明柿冲。
其實(shí),閉包指延伸了作用域的函數(shù)兆旬,其中包含函數(shù)定義體中引用假抄、但是不在定義體中定義的非全局變量。函數(shù)是不是匿名的沒有關(guān)系丽猬,關(guān)鍵是它能訪問(wè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()
avg(10)
avg(11)
avg(12)
注意,series 是 make_averager 函數(shù)的局部變量脚祟,因?yàn)槟莻€(gè)函數(shù)的定義體中初始化了series:series = []谬以。可是由桌,調(diào)用 avg(10) 時(shí)为黎,make_averager 函數(shù)已經(jīng)返回了,而它的本地作用域也一去不復(fù)返了行您。在 averager 函數(shù)中铭乾,series 是自由變量(free variable).
綜上,閉包是一種函數(shù)邑雅,他會(huì)保留定義函數(shù)時(shí)存在的自由變量的綁定片橡,這樣調(diào)用函數(shù)時(shí)妈经,雖然定義作用域不可用了淮野,但是仍能使用那些綁定捧书。注意,只有嵌套在其他函數(shù)中的函數(shù)才可能需要處理不在全局作用域中的外部變量骤星。
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):
...
UnboundLocalError: local variable 'count' referenced before assignment
這是怎么回事经瓷?對(duì)比之前的例子發(fā)現(xiàn),我們的自由變量的類型是列表洞难,現(xiàn)在是一個(gè)不可變的基本類型舆吮。當(dāng)我們?cè)谇短缀瘮?shù)中進(jìn)行賦值操作時(shí),其會(huì)試圖把它當(dāng)做局部變量队贱。報(bào)錯(cuò)也就不足為奇了色冀。為了解決這個(gè)問(wèn)題,python3引入了nonlocal聲明柱嫌,他的作用是把變量標(biāo)記為自由變量锋恬,即使在函數(shù)中為變量賦予了新值,也會(huì)變成自由變量编丘。如果為nonlocal聲明的變量賦予新值与学。閉包中保存的綁定會(huì)更新。
def make_averager():
count = 0
total = 0
def averager(new_value):
nonlocal count, total
count += 1
total += new_value
return total / count
return averager