標籤:

【Python】理解"閉包"

「閉包」是什麼,以及,更重要的是,寫『』閉包」有什麼用處。

(個人理解)

1、「閉包」是什麼

首先,明確函數的作用域問題:

(1)外層函數f1可以調用內層函數f2,但無法引用f2內部的變數x

(2)內層函數f2可以引用外層函數f1的變數y

def f1(y): def f2(x): return x+y #內層函數f2可以引用外層函數f1的變數y return f2()#外層函數f1可以調用內層函數f2 #return x 但不能引用f2內部的變數x,這樣寫就錯了

(3)如果把全局當做根函數(我自己提出的一個概念),那麼就有:

全局之於f1,等效於f1之於f2

理解三個層次,從外到內分別是:global-f1-f2

也就是說,在全局可以調用f1,但無法引用f1內部的y;f1可以引用全局變數x

x=5def f1(y): return x+y #f1可以引用全局變數xf1(1)#全局可以調用f1#print(y)但無法引用f1內部的y,這樣寫就錯了

(4)在外層函數里調用內層的變數,how to?

比如,想在global里調用f1內部的變數,怎麼辦?

思路:只能很曲折地。本來在global只能調用到f1,而不能調用f1內部的變數。

於是只能讓調用f1的時候,f1就把內部的變數主動交出來,也就是return 想要訪問的變數。

def f1(): y=1 return yfoo=f1()

以上就是在全局調用了f1(),f1返回就是y,就這樣比較曲折地,全局的foo還是拿到了f1內部的變數y的值。

以上是拿變數,把變數改成函數,也就是在global里想拿到f1內部定義的函數,思路是一樣的:

本來在全局是拿不到f1內部的任何東西,但只要f1把內部的東西返回,就可以通過在全局調用f1,來拿到f1內部的東西。

def f1(): def f2(x): return x return f2g=f1()g(5)

(5)閉包來了

以上,通過f1主動把f2返回,全局的g拿到了本來拿不到的f1作用域內部的f2函數,這樣相當於就可以在全局調用f2了。不過,這還不是閉包。因為這時,你在全局拿到的f2,就是f2本身,跟f1可以說沒有關係了。你在執行g=f1()完畢後,f1就被回收了,變數g指向函數f2。後來的g(5)就是在調用f2這個函數,傳入參數5,執行。

閉包是更糾結一點的情況:在全局拿到f2,且這個f2還跟f1還有關係。

先明確一下,函數內的變數和函數定義時的形參,作用域完全相同。

def f1(y): #y=5 想說y和y的作用域完全相同,只不過y作為變數取值是固定的,y作為形參取值由傳入實參決定,是可變的。但這裡我們討論的是作用域而不是取值 def f2(x): #x=1 x和x的作用域也是完全相同的 return x+y return f2

因此為了簡單說明問題,我們統一寫作函數內定義變數。

def f1(): y=5 def f2(): x=1 return x+y return f2g=f1()g()

這裡有三個層次,從外到內分別是:global-f1-f2

其中,

global作用域里有變數g,和函數f1

f1的作用域里有變數y,和函數f2

f2的作用域里有變數x

根據上面的規則,內層可以引用外層的變數,所以f2這一層里雖然沒有變數y,但是引用了f1這一層的y,這個是符合規則的。

因為解釋器知道f2依賴於變數y,因此,g=f1()執行後,f1和變數y還不會被回收,但是又不是作用域f2裡面的東西,成為「自由變數」。

此時,g就是一個閉包,因為它符合閉包是"引用了自由變數的函數的定義"。g這個閉包包含f2的定義,而f2引用了自由變數y

不過這種寫法沒有價值,因為g()的值永遠是6。

一般的用法是g()得是變數,比如實現計數器

def hellocounter (): count=[0] def counter(): count[0]+=1 print(Hello,,,count[0], access!) return counterhello=hellocounter()hello()hello()hello()#輸出是1 2 3#hello是閉包函數,自由變數是count,因為它不是counter這個函數內部的變數,但是卻被counter引用。

閉包一定要有自由變數,這個自由變數產生有兩個條件:一是內層函數去引用了外層函數的變數,二是內層函數被調用時,外層函數已經不見了,所以變數才會自由。

2、閉包的應用場景

1、保護函數內的變數安全。函數f1中y只有函數f2才能訪問,而無法通過其他途徑訪問到,因此保護了y的安全性。

2、在內存中維持一個變數。依然如前例,由於閉包,函數f1中y的一直存在於內存中,因此每次執行c(),都會給y自加1。

3、閉包會在父函數外部,改變父函數內部變數的值。也就是,在f1的外部,通過g改變f1內部的變數/函數f2的值。

所以,如果把父函數當作對象(object)使用,把閉包當作它的公用方法(Public Method),把內部變數當作它的私有屬性(private value),這時一定要小心,不要隨便改變父函數內部變數的值。

也就是如果把f1看成一個對象,把g看做對象的公有方法,f2看做對象的私有屬性,那麼這時相當於可以做到:通過公有方法改變私有屬性,一般是不建議的,違反了類的封裝性,使用要小心。


推薦閱讀:

更改Django用戶密碼[備忘]
用 Python 可以建網站嗎?
廢話少說:Python 這麼牛逼的 6 個原因是?
Python庫Numpy里ndarray.ndim 是什麼意思?
在Mac系統下python如何安裝第三方函數庫?

TAG:Python |