【Python語法】循序漸進理解閉包
循序漸進理解閉包
1. 閉包初接觸
在一個內部函式中,對外部作用域的變數進行引用,(並且一般外部函式的返回值為內部函式),那麼內部函式就被認為是閉包。
1.1 閉包基本語法結構:
# 外部函式返回內部函式
def outside(attr1):
# 內部函式使用了外部函式的變數
def inside(attr2):
return attr1 + attr2
# 外部函式返回內部函式
return inside
# 因為outside()返回的是函式,所以a也是一個函式
a = outside(1)
print(a)
# 因此可以對函式傳入引數,按照函式的執行順序,返回
print(a(2))
結果:
<function outside..inside at 0x00000229B7D93828>
3
通過閱讀程式碼註釋,可以理解最終的執行結果,但是要深入理解閉包,還需要往下看
1.2 明確變數作用域:
閉包中可能會涉及到內部函式訪問外部函式或者全域性的變數,複習下Pyhon變數作用域,為學習閉包語法掃清障礙:
- 內部函式無法修改外部函式的值:因為內部函式的變數作用域不在外部
def outter():
x = 1
def inner():
x = 2
print('x in inner: %d' % x)
print('x in outter before call inner: %d' % x)
inner()
print('x in outter after call inner: %d' % x)
o = outter()
結果
x in outter before call inner: 1
x in inner: 2
x in outter after call inner: 1
- 閉包中的訪問外部變數:nonlocal或者global
nonlocal 語句會去搜尋本地變數與全域性變數之間的變數,其會優先尋找層級關係與閉包作用域最近的外部變數。
attr1 = 1
attr2 = 11
def outfunc():
attr1 = 2
attr2 = 22
def inFunc():
nonlocal attr1
global attr2
attr1 += 10
attr2 += 100
print('inner attr1: %d' % attr1)
print('inner attr2: %d' % attr2)
print('outter attr1 before call inFunc: %d' % attr1)
print('outter attr2 before call inFunc: %d' % attr2)
inFunc()
print('outter attr1 after call outFunc: %d' % attr1)
print('outter attr2 after call outFunc: %d' % attr2)
test = outfunc()
結果:
outter attr1 before call inFunc: 2
outter attr2 before call inFunc: 22
inner attr1: 12
inner attr2: 111
outter attr1 after call outFunc: 12
outter attr2 after call outFunc: 22
2. 從for迴圈開始
2.1 python for迴圈特性:沒有域的概念
先閱讀一段程式碼
flist = []
for i in range(3):
def innerfunc(x):
return x*i
flist.append(innerfunc)
for f in flist:
print(f(2))
讓我們猜猜最終的結果,應該是2乘以列表[0,1,2]的每個元素,得到[0,2,4]
執行結果:
4 4 4
加斷點分析:
- 第一個for迴圈的元素結果是一個列表,列表元素都是函式,生成列表後,列表中的引數都不賦值,也不進行計算
flist = [<function innerfunc at 0x0000023575C390D8>,
<function innerfunc at 0x0000023575C39168>,
<function innerfunc at 0x0000023575C2EDC8>]
可以認為是 [x * i, x * i, x * i],此時作用域中i為2, 即列表為[x* 2, x * 2, x * 2]
- 第二個for迴圈遍歷flist,傳入引數x的值,此時列表結果即為[2 * 2, 2 * 2, 2 * 2]
2.2 修改程式碼,讓返回的flist具有遞增相乘的結果:
flist = []
for i in range(3):
def innerfunc(x):
return x*i
flist.append(innerfunc)
# 在第一次迴圈的時候,就對flist中的函式賦值,即避免多次遍歷flist
print(flist[i](2))
執行結果:
0
2
4
2.3 使用閉包
閉包的外部函式可以保證每次給函式列表flist新增元素時,i值已經固定
flist = []
for i in range(3):
def makerfun(i):
def fun(x):
return x*i
return fun
flist.append(makerfun(i))
for f in flist:
print(f(2))
加斷點分析:
- flist = [<function makerfun..fun at 0x000002DB3E22A168>, <function makerfun..fun at 0x000002DB3E21FDC8>, <function makerfun..fun at 0x000002DB3E21FCA8>]
flist依舊是一個函式列表:[makerfun(0), makerfun(1),makerfun(2)]
又因為列表元素是一個閉包,即[x * 0, x * 1, x * 2]
在第一個for迴圈結束後,i的值已經固定下來了
- 第二個for迴圈flist,傳入引數x的值,此時列表結果即為[2 * 0, 2 * 1, 2 * 2]
執行結果:
0
2
4
體會過上面的例子後,就很好理解閉包的作用:儲存當前的執行環境
拿上面的例子來講,x始終是我們計算時要傳入的變數,i看做每次for迴圈執行的環境標識。
閉包實現了讓外部函式儲存了每次for迴圈的環境標識,當我們回頭再為flist的每個元素函式傳入變數x時,環境標識被閉包固化。
3. 玩棋盤遊戲
模擬棋盤遊戲,使用閉包,讓外部函式實時儲存出發的座標,內部函式完成棋子按照指定方向和步長移動的任務
origin = [0, 0]
def create(pos=origin):
def go(direction, step):
pos[0] += direction[0] * step
pos[1] += direction[1] * step
return pos
return go
player = create()
print(player([1, 0], 10))
print(player([0, 1], 10))
print(player([-1, 0], 10))
看到這段程式碼是不是對閉包的語法有了更深的理解,注意對照這句話體會:
閉包持有外部函式的變數,這個變數叫做自由變數,當外部函式的宣告週期結束後,自由變數依然存在,因為它被閉包引用了,所以不會被回收
4. 閉包特性的另一種實現
閉包這個特性可以用類實現,把類變數看做是自由變數,類的例項持有類變數
但是使用閉包會比使用類佔用更少的資源,自由變數佔用記憶體的時間更短
class Animal():
def __init__(self,animal):
self.animal = animal
def sound(self,voice):
print(self.animal, ':', voice)
dog = Animal('dog')
dog.sound('wangwang')
dog.sound('wowo')
def voice(animal):
def sound(voc):
print(animal,':', voc)
return sound
dog = voice('dog')
dog('wangwang')
dog('wowo')
可以看到輸出結果是完全一樣的,但顯然類的實現相對繁瑣,且這裡只是想輸出一下動物的叫聲,定義一個 Animal 類未免小題大做,而且 voice 函式在執行完畢後,其作用域就已經釋放,但 Animal 類及其例項 dog 的相應屬性卻一直貯存在記憶體中
5. 閉包實現流水線作業
(待補充)
相關文章
- 循序漸進理解TypeScript型別模式TypeScript型別模式
- 循序漸進學加密加密
- 循序漸進linux(二)Linux
- 務實發展,循序漸進
- 循序漸進DIY一個react(二)React
- 循序漸進DIY一個react(四)React
- 循序漸進DIY一個react(三)React
- 循序漸進DIY一個react(一)React
- 理解Python函式閉包Python函式
- Matplotlib學習筆記2 - 循序漸進筆記
- 循序漸進掌握遞迴正規表示式遞迴
- python閉包 - 理解與應用Python
- python進階(12)閉包Python
- 用“揹包”去理解Go語言中的閉包Go
- 循序漸進的用js實現一個bind()JS
- 理解“閉包”
- 使用C#的後端Web API:循序漸進教程後端WebAPI
- 循序漸進 Redis 分散式鎖(以及何時不用它)Redis分散式
- Python——五分鐘理解函數語言程式設計與閉包Python函數程式設計
- 理解JavaScript 閉包JavaScript
- Groovy閉包理解
- 【譯】JavaScript進階 從實現理解閉包JavaScript
- [Python小記] 通俗的理解閉包 閉包能幫我們做什麼?Python
- 自動化響應要循序漸進 不能一蹴而就
- 漸進深入理解NginxNginx
- js閉包的理解JS
- 深入理解閉包
- PHP 閉包的理解PHP
- 理解Javascript的閉包JavaScript
- Python進階之閉包和裝飾器Python
- 理解 JavaScript 中的閉包JavaScript
- 對javascript閉包的理解JavaScript
- Golang中閉包的理解Golang
- 深入理解JS閉包JS
- 對JS閉包的理解JS
- 循序漸進學.Net Core Web Api開發系列【14】:異常處理WebAPI
- 循序漸進學.Net Core Web Api開發系列【4】:前端訪問WebApiWebAPI前端
- 《程式設計的原則》重新發明車輪感悟之循序漸進程式設計