033-閉包

先來(lái)做兩個(gè)實(shí)驗(yàn),加深一下對(duì)外層變量和內(nèi)層變量的區(qū)別和關(guān)聯(lián):

>>> def funa():
    x = 88
    def funb():
        x = 99
    return print(x)

>>> funa()
88

----------
在這個(gè)案例中,雖然內(nèi)層函數(shù)定義了另一個(gè)變量x,但最終返回的,仍然是外層函數(shù)的變量x。因此x = 88.
>>> def funa():
    x = 88
    def funb():
        x = 99
        print(x)
    return funb()

>>> funa()
99

----------
在這個(gè)案例中,print(x)是funb()的一部分,因此最后返回的是funb()中定義的x,因此是99.

上面兩個(gè)例子都有一個(gè)共同點(diǎn),就是內(nèi)層函數(shù)和外層函數(shù)都沒(méi)有設(shè)定參數(shù)。如果函數(shù)設(shè)定了參數(shù),會(huì)是怎么樣的情況呢?這個(gè)等下舉例的時(shí)候會(huì)提到。

閉包:如果一個(gè)內(nèi)層函數(shù)調(diào)用了他的外層函數(shù)的參數(shù)。那么我們稱(chēng)這個(gè)內(nèi)層函數(shù)和被他調(diào)用的參數(shù)為一個(gè)閉包。

閉包概念示意圖

舉個(gè)例子:

>>> def funa(x):
    def funb(y):
        return x * y
    return funb

>>> a = funa(9)
>>> a(6)
54

從上面例子可以看到,x這個(gè)外層函數(shù)的參數(shù),在內(nèi)層函數(shù)中被調(diào)用。最終返回的是funb,而非funb(),原因是funb這個(gè)內(nèi)層函數(shù)是有參數(shù)y的,如果返回funb()就相當(dāng)于缺少了y,沒(méi)有給y這個(gè)參數(shù)賦值,最終就會(huì)導(dǎo)致報(bào)錯(cuò)。

>>> def funa(x):
    def funb(y):
        return x * y
    return funb()

>>> a = funa(8)
Traceback (most recent call last):
  File "<pyshell#17>", line 1, in <module>
    a = funa(8)
  File "<pyshell#16>", line 4, in funa
    return funb()
TypeError: funb() missing 1 required positional argument: 'y'

一開(kāi)始我完全沒(méi)有看懂為什么需要輸入一個(gè)a = funa(8),然后在輸入一個(gè)a(9),后來(lái)明白了。輸入funa(8)相當(dāng)于調(diào)用了funa(x),并且將x賦值為8,外層函數(shù)內(nèi)部嵌套了一個(gè)內(nèi)層函數(shù)funb(y),運(yùn)行到這里的時(shí)候,就需要你給funb(y)里面的y一個(gè)賦值。那么這個(gè)賦值要如何賦呢?肯定不能直接funb(9)這樣子,因?yàn)閒unb()作為一個(gè)內(nèi)層函數(shù),是不能在全局環(huán)境中直接調(diào)用的。

而程序已經(jīng)通過(guò)設(shè)計(jì)解決了這個(gè)問(wèn)題。外層函數(shù)返回的結(jié)果本身就是funb,此時(shí)的funb就等于a(外層函數(shù)返回的結(jié)果),那么a(9)自然也就相當(dāng)于funb(9)了。

其實(shí)這么寫(xiě)可能更簡(jiǎn)單一些:

>>> def funa(x):
    def funb(y):
        return x * y
    return funb

>>> funa(8)(9)
72

教科書(shū)上沒(méi)有這么寫(xiě),這是我通過(guò)理解之后推導(dǎo)出來(lái)的哦。我可真棒!!


image

正如上一節(jié)所說(shuō),python并不希望你在函數(shù)內(nèi)部修改全局變量,或者在內(nèi)層函數(shù)中修改外層函數(shù)的變量??扇绻阋灰夤滦校且膊皇遣荒芨?,只是在內(nèi)層函數(shù)中對(duì)變量賦值的時(shí)候,要做一下全局聲明,也就是要加上一個(gè)global。

在閉包中也是如此,如果你想在內(nèi)層函數(shù)中對(duì)外層函數(shù)的變量進(jìn)行修改,你需要先對(duì)變量進(jìn)行一個(gè)聲明,即在要修改的變量名前面加上nonlocal。例:

>>> def funa():
    x = 5
    def funb():
        nonlocal x
        x = x + 1
        return x
    return funb

>>> funa()()
6

當(dāng)然,這是python3中的用法,而在python2中可沒(méi)有這么智能。我們說(shuō)外層函數(shù)中x=5,內(nèi)層函數(shù)中,x=6,這時(shí)候內(nèi)層函數(shù)并不是改變了外層函數(shù)的變量值,而是重新生成了一個(gè)名稱(chēng)相同但id不同的變量x,并給他賦值為6。為什么要這樣操作?因?yàn)樵谶@個(gè)過(guò)程中,x這個(gè)變量是放在棧里的。因此要?jiǎng)?chuàng)造一個(gè)新的x,來(lái)避免將原來(lái)的x所覆蓋。

可我們有的時(shí)候就是要覆蓋原來(lái)的x,就是要在內(nèi)層函數(shù)中修改外層函數(shù)的變量值。那咋辦呢?可能你已經(jīng)想到主意了。我們找一個(gè)不放在棧里的東西來(lái)表示變量x不就行了嗎?

那么那種類(lèi)型的數(shù)據(jù)是不放在棧里的呢?序列!

因此,假如我們讓外層函數(shù)的x = [5],在內(nèi)層函數(shù)中,讓x[0] = x[0] + 1這就完美地逃脫了原來(lái)規(guī)則的束縛。舉例如下:

>>> def funa():
    x = [5]
    def funb():
        x[0] = x[0] + 1
        return x[0]
    return funb

>>> funa()()
6

由于python3已經(jīng)幫我們解決了這個(gè)問(wèn)題了,因此這個(gè)投機(jī)取巧的辦法學(xué)習(xí)其思想,能看懂,就OK了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容