求幫助,Python閉包和返回函數問題?

Python新手,在學習過程中遇到問題我在看這個舉例的時候看的非常不懂,求大神幫助解答以下問題:

1.Python的閉包就是返回函數嗎?這個舉例的是一個返回列表fs?列表也可以作為函數嗎?

2.返回函數運行機制不懂啊,比如這個例子,count函數,然後返回fs列表,這樣計算列表嗎?為什麼不直接把列表fs返回了?還有為什麼運行結果是9,9,9,看不懂。

3.舉例說f1()是調用函數,調用函數不是f1()括弧內賦值調用嗎?還有怎麼莫名其妙跑出來一個f1?沒有定義過吧。。。

謝謝大神解答,Python新手一枚,問題好多。。。。感覺自己要學的還有好多好多,謝謝謝謝!


幾個函數位於同一個 closure 里,所以最後使用的是同一個變數——變數的值在 outer 函數執行過程中被變成最後一個值,就是你看到的。

Q.E.D.(順便,你補充里的幾個問題只說明……你對 Python 語法對理解還沒到可以看 closure 這些東西的時候。什麼叫「計算列表」?什麼叫「直接把列表fs返回了」?什麼叫「括弧內賦值調用」?還有 f1, f2, f3 = count() 是啥?——先把這些搞明白了,再研究「為什麼結果都是 9」。


我覺得文中的例子對初學者不太友好,裡面同時混雜幾個不同的問題。

因為涉及問題太多了,我也不期待能夠一下說明白,儘力而為。

(1)unpack tuple和list, 可以讓函數返回多個值

def count():
return (1, 2, 3) # 或者 return [1, 2, 3]

# 把列表解包,把1 2 3 分別賦值給 a b c
a, b, c = count()
print a, b, c
# 輸出 1, 2, 3

(2)假設你知道Python的dict類型。Python中,在函數中定義一個變數的時候,會在一個隱藏的叫locals的dict裡面插入key-value,其中key是變數名,value是變數值。而引用一個變數的時候,則首先會在這個叫locals的dict裡面,根據變數名作為key,去查對應的值。

var = 1 # 你可以認為這裡進行了 locals["var"] = 1 的操作
print var # 在對var變數進行求值的時候,就在locals["var"]裡面找var變數對應的值

(3)for循環中,每次循環只是給 `i` 重新綁定值

for i in (1, 2, 3):
print i

print i
# 一次輸入 1 2 3 3

每次`for i in (1, 2, 3)`相當於在`print i`之前,進行了

`locals["i"] = 1`

`locals["i"] = 2`

`locals["i"] = 3`

的操作

所以最後的`print i`再去locals字典裡面找`i`的時候,就變成 3 了。

(4)閉包是 一個函數加上這個函數引用的外部變數

var = 1
def f():
print var
# 這裡的閉包是函數 f 和 f 引用的外部變數 var

def count():
var2 = 2
def f():
print var2
# 這裡的閉包是函數 f 和 f 引用的外部變數 var2
return f

拿第一個函數 f 來說。在 f 運行的時候,解釋器拿著"var"這個字元串去locals字典裡面找,發現找不到,於是在closure字典裡面找,最後closure字典裡面找,你可以認為就是找closure["var"],然後發現找到對應的值。count裡面的 f 函數同理。

(為了容易理解,我這裡說謊了。實際上 f 壓根沒有closure,count裡面的 f 才有。其實closure壓根不是像locals那樣的字典)

(5)函數定義時,函數只是記錄變數的名字。

要區分什麼是名字,什麼是值。

`i = 1`這裡 i 只是名字,只是一個字元串 "i" 。這句話運行完,locals["i"] = 1,就說 i 對應的值是1

def count():
fs = []
for i in range(1, 4):
# 定義一個函數,等價於運行了 locals["f"] = 真正生成的函數
# 每次循環,這裡都會重新生成一個函數,然後把重新生成的函數賦值給 locals["f"]
def f():
return i * i # 引用了"i"這個名字,但並不是引用了"i"對應的值

# 等價於 locals["fs"].append(locals["f"])
# f 不是函數,它只是一個名字"f"。f 引用的東西,也就是locals["f"]才是真正的函數
fs.append(f)
# 於是這個for循環生成了三個函數,這三個函數是沒有名字的,這個函數運行完後,它們跟"f"這個名字就毛關係都沒有了(是的我說慌了,但可以先不管)
# 把整個列表返回,這個列表包含了三個函數
return fs

# count()返回三個函數的列表,unpack 列表的語法把列表中的三個函數抽出來,重新給他們命名為 f1, f2, f3
# 也就是說,
# locals["f1"] = 列表中的第1個函數
# locals["f2"] = 列表中的第2個函數
# locals["f3"] = 列表中的第3個函數
# 這三個函數跟"f"這個名字現在毛關係都沒有。(其實是有的,但為了說明需要簡化,現在你可以完全不管括弧裡面說的話)
f1, f2, f3 = count()
print f1(), f2(), f3()
# 好了我們運行它們,輸入都是 9

# def f():
# return i * i

這是因為 f1 現在對應的函數,裡面引用了 "i" 這個字元串,我們根據 "i "這個字元串去找它對應的值,先找到 f 當前的locals字典,發現沒有,因為函數定義的時候沒有定義 i 變數。然後再去closure["i"]裡面找,因為Python是通過closure字典實現閉包的(就當它是對的好不好),所以我們可以在closure["i"]找到值,這個值就是我們上一次運行的時候count函數裡面殘留的locals["i"],而由於for循環三遍之後,locals["i"] == 3,所以找到 i 的值就是3。所以最後輸出都是9


1、如果是因為看廖雪峰大神的教程看到這裡卡住的話,可以看下我的理解參考一下。

在前面這裡原來教程有兩段代碼

def calc_sum(*args):

ax = 0

for n in args:

ax = ax + n

return ax

這裡實際上是傳入一個可變參數*args,求args這個list中的值的和,這個你理解起來肯定沒問題,(*nums表示把nums這個list的所有元素作為可變參數傳進去。這種寫法相當有用,而且很常見。)然後教程接下來引入返回函數和閉包的概念,

def lazy_sum(*args):

def sum():

ax = 0

for n in args:

ax = ax + n

return ax

return sum

這個函數和上面的區別在於哪裡呢,就是上面函數改名為sum,在這個lazy_sum函數中定義的,這樣這個函數其實是包含sum的定義和返回sum這個函數兩部分組成,這裡其實並沒有sum的調用,執行這個lazy_sum函數的時候,並不會返回args這個list的求和,只是將args這個list中的各個元素作為參數傳遞給lazy_sum中的sum函數作為參數,存在sum函數的這個作用域中。我的理解,此時的sum就是這個lazy_sum函數返回的函數,也就是引入的「閉包」。

2、再來說回你這裡的代碼,逐行給你解釋

def count(): #定義一個count函數

fs = [] #初始化一個空的列表list,名為fs

for i in range(1, 4): #for循環,對1,2,3這三個值進行迭代

def f(): #定義f函數

return i*i #返回值是i*i

fs.append(f) 將f這個函數,也就是上面的閉包,添加到fs這個list的末尾

return fs 返回fs這個list,這個list包含三個元素,第一個是f(i=1),第二個f(i=2),第三個f(i=3),這是count()的返回值,由於i是變數,所以list中三個元素都是f(i=3)

f1, f2, f3 = count() # "高級特性,a,b,c = [1,2,3] ##a=1,b=2..." 將上面list的三個元素,也就是count()函數的返回值list分別賦值給三個新定義的函數名f1,f2,f3,這就是把f(i)賦值給f1,f2,f3了,所以下一步執行f1()就比較好理解了。這時i為3,所以f1,f2,f3的值都是3*3=9

f1()結果是9,這樣就比較好理解了。這就回答了你第2和第3個問題了。』

3、理解了這兩個問題,再去看原教程中的剩餘部分就好理解了,如果有不懂的,歡迎評論交流。


閉包就是自帶BGM的男人

這個BGM叫運行環境,一般是一堆變數

```python

def foo():

a=1

b=2

def bar():

print(a+b)

return bar

#注意不是return bar()

print([cell.cell_contents for cell in foo().__closure__])

#輸出是[1,2]

```

所謂的closure就是這些`BGM`,而且是按照與目標函數(此處是bar)的距離壓棧的,壓的是cell對象

雖然用字典來表明closure很容易理解,但是我覺得用棧來理解更好的說明為什麼這個作用域叫closure(閉包),這裡的close可以理解為接近,而不是關閉

至於閉包是什麼意思,目標就是自帶運行環境,不讓外部變數(local,global,build-in)污染運行環境,這一點在JS中尤其能體現


謝邀

首先感謝,終於不是caffe和CNN的問題了。。。另外,我也是python入門,用我的理解給你解釋下吧。

首先,什麼是閉包,

在計算機科學中,閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變數的函數。這個被引用的自由變數將和這個函數一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法認為閉包是由函數和與其相關的引用環境組合而成的實體。閉包在運行時可以有多個實例,不同的引用環境和相同的函數組合可以產生不同的實例。

好吧,看暈了么,我也暈了。。。簡單說吧,一個閉包就是你調用了一個函數A,這個函數A返回了一個函數B給你。這個返回的函數B就叫做閉包。你在調用函數A的時候傳遞的參數就是自由變數。

OK,我們來看你這個例子。f()就是一個閉包。這個閉包有自己的變數,但是有個什麼問題呢?就是這個閉包返回的i*i,是傳遞給count()的。那麼當你調用count時,你需要返回幾個值,其實f()是運行了幾次的。好吧,這句說的不太明白,詳細說,count返回了三個值,f1 f2 f3 此時i已經是3了,i發生了改變。可是f1 卻並沒有計算,當你輸入f1()時,f1才開始計算i*i,可此時i已經是3了。這個閉包失敗的原因就是i發生了改變。


感覺我已經理解清楚了,來回答下……

1、列表不是函數。一般1個閉包返回1個函數,這裡你可以理解為3個函數是3個閉包,想要3個一起返回,但是返回只能有1個參數,怎麼辦呢?只好用裝在列表裡返回。你看返回後的列表也是拆給了f1,f2,f3。

2、回答第二個問題,需要區分count和count(),不加括弧是函數本身,加括弧是函數運行後的返回值。首先f1,f2,f3=count(),右邊的count()是調用函數count的返回值,也就是列表,列表中的元素是函數,所以結果就是f1,f2,f3各自拿到一個函數。下一句f1(),就是執行這個函數……

3、f1,f2,f3=count(),f1在這裡定義過。

4、最關鍵的149還是999的問題,我覺得是其實是時間線問題。有個答案把fs.append(f)改成了fs.append(f()),結果就不同了,會得到149.這是因為appnd()中增加的f()是函數返回值,也就是說函數定義後當場被調用了,此刻在時間上還位於for循環的這一輪當中。

而提問中,fs.append(f),此刻在時間上只發生了定義,時間往後走,for循環全部結束。時間再往後走,f才被調用,調用時f才開始思考i等於多少的問題……i是變數,變化就是隨時間而變化,具體說就是在for的每一輪開始這個時間點加1。在這個後來的時間點上i已經增加到3,所以f只能看到i等於3,而且它無從得知在過去的時間裡,i有過哪些歷史值。


其實是同一個函數f被保存了列表的三個位置上。[f,f,f]

i最後一次是3.

自然會得到9的結果。

這個例子不好。


大神好,剛碰到一個閉包問題,看到知乎,前來請教。code跟大神的一樣

不幸的是,我的結果為

所以後面的就沒再看了,用的Python 2.7.6, ubuntu14.04, 如有錯誤還請指正,嘿,thanks~


推薦閱讀:

強人工智慧的產生是否離不開數理邏輯的支撐?
如何紮實地學習Lisp?
具體的講,C#相比JAVA有哪些先進的地方?
計算機專業不學c語言是怎樣的存在?
程序員怎麼學習英語?

TAG:Python | 編程 | Python入門 |