【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型別模式
- python學習: 如何循序漸進學習Python語言Python
- Docker循序漸進Docker
- 循序漸進學加密加密
- 循序漸進linux(二)Linux
- SQL開發 循序漸進SQL
- 循序漸進學習oracleOracle
- Linux循序漸進(09)(轉)Linux
- Linux循序漸進(08)(轉)Linux
- Linux循序漸進(04)(轉)Linux
- Linux循序漸進(07)(轉)Linux
- Linux循序漸進(06)(轉)Linux
- Linux循序漸進(05)(轉)Linux
- Linux循序漸進(03)(轉)Linux
- Linux循序漸進(02)(轉)Linux
- Linux循序漸進(01)(轉)Linux
- Linux循序漸進(16)(轉)Linux
- Linux循序漸進(13)(轉)Linux
- Linux循序漸進(11)(轉)Linux
- Linux循序漸進(10)(轉)Linux
- Mongodb操作之查詢(循序漸進對比SQL語句)MongoDBSQL
- 循序漸進DIY一個react(二)React
- 循序漸進DIY一個react(一)React
- 循序漸進DIY一個react(三)React
- 循序漸進DIY一個react(四)React
- Linux循序漸進(22):vi(轉)Linux
- Linux循序漸進(19):shell(轉)Linux
- 怎樣黑進Microsoft:循序漸進指南 (轉)ROS
- 理解Python函式閉包Python函式
- 循序漸進Oracle - 全面認識Oracle ASHOracle
- SOA治理最佳策略:小幅起步循序漸進
- Jsp+JavaBean循序漸進教程(六)JSJavaBean
- Linux循序漸進(24):vi命令(轉)Linux
- Linux循序漸進(21):別名(轉)Linux
- Linux循序漸進(17):列印操作(轉)Linux
- Linux循序漸進(15):磁碟管理(轉)Linux
- IT專案外包要注意循序漸進(轉)
- python閉包 - 理解與應用Python