Python 閉包代碼理解?

def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs

f1, f2, f3 = count()
#這段代碼的流程能敘述下嗎,剛接觸,有點暈


調用count執行完,i已經是3了,def f()中的代碼並沒有執行,後期再調用f1() f2() f3(),結果就是9了啊。


1,python裡面所有都是對象,函數也是,你把閉包函數當成一個變數對象來理解,就很清楚了
2,和其他普通對象比如字元串,數字的區別在於,這個閉包函數對象是callable的,而且call的過程中還能引用「外面」的變數,比如題目例子中的i
3,之所以2種「外面」兩個字加引號,是因為看起來是外面,實際不是外面,他們都在一個命名空間,你隨便聲明一個a=1,再看locals()裡面肯定有a在裡面


#!/usr/bin/env python
#coding:utf-8

def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs

f1, f2, f3 = count()
print f1(), f2() ,f3()

print f1.__closure__[0].cell_contents # 列印閉包值 即i的值 =3

def count():
fs = []
for i in range(1, 4):
def f(j=i):
return j*j
fs.append(f)
return fs

f1, f2, f3 = count()
print f1(), f2() ,f3()

print f1.__closure__ # 沒有閉包,因為外部便利i已經傳值給默認參數j了

我對閉包的理解,
當函數存在嵌套,並且子函數引用了父函數中的變數,可以訪問這些變數的作用域就形成閉包。2 如果子函數沒有訪問父函數中的變數,就不存在閉包。
打個比方:
一個大盒子,內部有一個小盒子,小盒子里用到一些東西是來自這個大盒子,那麼這些來自大盒子的東西,就是閉包。

回到例子:

第一種形式中,i並沒有在函數中定義,所以f 和外部i 構成閉包,i在range最後取值為3,因此在return fs 這一行的時候,這個閉包里i 的值確定了,每一次調用的結果都是9
第二種形式,i每個階段的值,通過默認參數傳入j,這時候相當於j拿下了這個接力棒,把中間值都保存下來了,這時候每一個f的構成,沒有任何閉包,return之後i就銷毀了
題主你體會一下這兩種形式的不同。

如果你不用閉包來定義:

f1, f2, f3 = [lambda :i*i for i in range(1,4)]
print i
print f1(), f2() ,f3()
i = 5
print f1(), f2() ,f3()

這裡所有的匿名函數,都會直接讀取全局變數i,因此全局變數i的值,結果也會跟著變。

那麼我們怎麼得到正確的結果? 一種方法是通過函數默認參數,相當於給一個接力棒:

f1, f2, f3 = [lambda j=i:j*j for i in range(1,4)]
print i
print f1(), f2() ,f3()
i = 5
print f1(), f2() ,f3()
print f1.__closure__

另一種方法就是通過閉包咯, 這裡的 get_fx 里定義了一個子函數,子函數用到了外部的number變數

def get_fx(number):
return lambda: number * number

f1, f2, f3 = [get_fx(i) for i in range(1, 4)]
print f1(), f2(), f3()
print f1.__closure__

&>&>&>(&,)
函數閉包

-

深入了解最好看看這裡 : What is a #x27;Closure#x27;?


請看黃哥漫談Python 閉包。
黃哥漫談Python 閉包。 - 通過python學會編程 - 知乎專欄


def count():
fs = []
for i in range(1, 4):
def f(j=i):
return j * j
fs.append(f)
return fs

f1, f2, f3 = count()
print(f1(), f2(), f3())

這是你需要的,結果

[1, 4, 9]

或者就用別的答案提到的 lambda。


閉包簡單來說就是 函數+上下文環境。它能捕獲函數外部的變數,持有變數所在環境的一個引用,或者說指針。 我對python不熟,不清楚它的具體閉包實現是怎麼樣的,看@鬍子的答案,應該就是把上下文環境保存在_closure_里,我有空查查資料。

這個例子的流程的話,就是fs里放了三個閉包,它們都持有對count()的引用(就是_closure_吧),所以count()不會被銷毀。三個閉包被分別賦值給f1,f2,f3。當你調用f1(),f2(),f3()的時候它們都會到count中去找i,count()之前已經執行完了,i的值為3,所以結果都為9。


《Learning Python 5th Edition》 Page 506,
小節 Loop variables may require defaults, not scopes
詳細分析了原理,想要深入理解的話最好把Chapter 17 Scope這一章啃下來,絕對一勞永逸。


剛才又仔細看了下代碼,有點感悟。
如果print(f1(),f2(),f3())的話應該是9,9,9
首先要明確一點,變數都是引用的內存的一個對象(自己的理解,不對請指出)
當程序進入循環,按道理說應該是返回1,4,9這樣一個東西
但是,我們返回的是函數,且這個函數在返回時並沒有執行,
這就意味著i變數指向內存里的那個對象已經為3了,
我們再次PRINT的時候,函數才執行得出結果,計算過程里i都為3,所以結果應該為9,9,9


py初學者,看了網上很多人對此的理解,感覺還是萌萌的。於是乎自己跟蹤對象調試了一波,感覺好像很有發現啊。

對於第一個無參數的:

發現每個函數對象中的func_closure其實是對同一個對象的引用,指向同一塊內存。

每個函數對象中的func_closure中的cell_contents都為3,而且注意到每個int object的地址也一樣,即每個3其實是同一個對象的值。

這是否意味著可以這麼理解閉包,即閉包主要就是說的函數對象中的這個func_closure,其本身也是一個對象,其為f1、f2、f3三個對象所引用的同一個包含上下文信息的對象,這裡上下文信息內容就是引用了同一個int object。閉包不僅僅是個概念,而且是實際在內存中存在的一個對象?

第二個有默認參數的:

此時函數對象中的func_closure都為None,而func_defaults中的值分別為1、2、3。也就是說在循環過程中並沒有保存func_closure,而是直接將值送給了func_defaults,作為函數調用時的默認參數。這是否意味著此時函數對象不需要額外的變數信息,而只需存下自己在定義時的默認值j。

如果以上實驗、猜測和結論正確的話,那就是說,閉包僅僅是一個為了保存上下文信息以便在函數調用時正確定址和取值的存在。

py初學者,根據別人的描述,和對調試結果的理解和猜測。


因為你fs.append(f)中f只是函數定義,fs列表中放的只是函數的聲明,類似聲明變數未賦值,所以你最後調用時,i的值變成3了,最後結果就都是9啦


雖然這問題時效性可能過了。我覺得根本沒有啥閉包那麼高深
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f())
return fs

f1 = count()
print(f1[0],f1[1],f1[2])

1 4 9
fs.append(f)------&>fs.append(f())
題主給出的例子,返回的是函數的地址,沒有執行。然後輸出的時候再調用執行f這個函數,這個時候i已經是3了,所以結果是999。
改了之後,返回的是函數的值,讓他在count時就把值算出來,就是1 4 9 了
願對後來的小白有些幫助


閉包什麼的是什麼含義就不解釋了吧,主要來看下代碼流程,到底幹了件啥事

f1,f2,f3=count() #表示執行了count函數,並把返回值賦給了f1,f2,f3

所以我們看下count()到底返回了啥

count()首先定義了一個列表,然後通過一個for循環給列表添加了3個元素,最後返回了列表

所以我們就知道f1,f2,f3分別代表這個列表fs的前三個值fs[0],fs[1],fs[2]

現在的關鍵就是列表的值到底是啥

所以我們看下列表中添加的到底是什麼元素,從for循環中看出,原來每次都把函數f這個對象添加到了列表中,f只是函數對象的一個引用,這個函數並沒有執行

因此我們知道,fs列表中存放的,是三個f指向的函數對象(我們暫且稱他為函數f),這個函數f的功能就是返回i*i

因此,f1,f2,f3分別指向了三個這樣的函數f

f1(),f2(),f3()就是將這三個函數f分別執行

現在問題又來了,函數f里使用到了i這個變數,這個變數來自於函數f定義時的父函數,並沒有來自他本身或者傳入他的參數——內部函數引用外部函數的局部變數(即使外部函數已經返回),就形成了閉包

所以這個來自函數f的外部函數的i究竟是什麼

當我們執行f1,f2,f3=count()時,count函數內的語句以此被執行,count函數執行完畢,返回fs,f1,f2,f3才等於fs[0],fs[1],fs[2]

所以f1(),f2(),f3()時,count函數已經執行完畢了,i變數已經經過循環,變成3了,現在執行return i*i語句,自然會得到9,9,9


這個了解Python的懶載入(lazy-loading)的話,好理解得多


在這裡x相當於一個自由變數,是在運行時綁定值,而不是在定義時綁定,應該是這個原因吧


剛剛學了閉包,就算是借題目小結一下吧。
前提:在Python中,函數也是一級對象,也就是:函數可以被變數賦值,也可以作為另一個函數的 參數;
閉包:內層函數調用了外層函數的變數,只有當外層函數被執行完內層函數被調用時,才會進行計算,因此,當f1()調用內層函數時,開始計算i*i,此時i尋找與它連接的外層函數中的變數,即3,所以均返回9


是不是可以這麼理解,fs列表裡保存了f函數,函數都是return i*i,
f1調用的時候執行了一次f,但是i值是3,就返回3*3了。


推薦閱讀:

系統中同時有 python2和 python3,怎麼讓 ipython 選擇不同的版本啟動?
想用Python做一款處理上市公司財務數據的軟體,應該學習哪幾個部分?
不懂編程,如何才能學好python呢?
大家都是怎麼部署python網站的?
python中如何使用requests模塊下載文件並獲取進度提示?

TAG:Python | Python3x | Python入門 |