給妹子講python-S01E19解析Python內嵌作用域與函式閉包

醬油哥在掘金發表於2018-08-16

歡迎關注公眾號:python資料科學家

【要點搶先看】

1.python中獨特的巢狀函式
2.巢狀作用域與閉包現象
3.nonlocal關鍵字與內嵌作用域變數修改

前情回顧,上一節我們介紹了變數的LEGB索引機制:對一個變數,首先在本地(函式內)查詢;之後查詢巢狀函式的本地作用域,然後再是查詢當前的全域性作用域。

到目前為止,我們還有一個作用域沒有介紹,就是巢狀作用域,即E,他是巢狀函式的本地作用域。

【妹子說】什麼是巢狀函式?

python有一個很有意思的地方,就是def函式可以巢狀在另一個def函式之中。呼叫外層函式時,執行到的內層def語句僅僅是完成對內層函式的定義,而不會去呼叫內層函式,除非在巢狀函式之後又顯式的對其進行呼叫。

x = 99

def f1():
    x = 88
    def f2():
        print(x)
    f2()

f1()

88
複製程式碼

可以看出,f1中的巢狀變數x覆蓋了全域性變數x=99,然後f2中的本地變數按照引用規則,就引用了x=88。

下面我們來說說巢狀作用域的一個特殊之處:

本地作用域在函式結束後就立即失效,而巢狀作用域在巢狀的函式返回後卻仍然有效。

def f1():
    x = 88
    def f2():
        print(x)
    return f2

action = f1()
action()

88
複製程式碼

這個例子非常重要,也很有意思,函式f1中定義了函式f2,f2引用了f1巢狀作用域內的變數x,並且f1將函式f2作為返回物件進行返回。最值得注意的是我們通過變數action獲取了返回的f2,雖然此時f1函式已經退出結束了,但是f2仍然記住了f1巢狀作用域內的變數名x。

上面這種語言現象稱之為閉包:一個能記住巢狀作用域變數值的函式,儘管作用域已經不存在。

這裡有一個應用就是工廠函式,工廠函式定義了一個外部的函式,這個函式簡單的生成並返回一個內嵌的函式,僅僅是返回卻不呼叫,因此通過呼叫這個工廠函式,可以得到內嵌函式的一個引用,內嵌函式就是通過呼叫工廠函式時,執行內部的def語句而建立的。

def maker(n):
    k = 8
    def action(x):
        return x ** n + k
    return action

f = maker(2)
print(f)

<function maker.<locals>.action at 0x00000000021C51E0>
複製程式碼

再看一個例子:

def maker(n):
    k = 8
    def action(x):
        return x ** n + k
    return action

f = maker(2)
print(f(4))

24
複製程式碼

這裡我們可以看出,內嵌的函式action記住了巢狀作用域內得兩個巢狀變數,一個是變數k,一個是引數n,即使後面maker返回並退出。我們通過呼叫外部的函式maker,得到內嵌的函式action的引用。這種函式巢狀的方法在後面要介紹的裝飾器中會經常用到。這種巢狀作用域引用,就是python的函式能夠保留狀態資訊的主要方法了。

這裡接著說說另一個關鍵字nonlocal

本地函式通過global宣告對全域性變數進行引用修改,那麼對應的,內嵌函式內部想對巢狀作用域中的變數進行修改,就要使用nonlocal進行宣告。

def test(num):
    in_num = num
    def nested(label):
        nonlocal in_num
        in_num += 1
        print(label, in_num)
    return nested

F = test(0)
F('a')
F('b')
F('c')

1
2
3
複製程式碼

這裡我們可以看到幾個點,我們在nested函式中通過nonlocal關鍵字引用了內嵌作用域中的變數in_num,那麼我們就可以在nested函式中修改他,即使test函式已經退出呼叫,這個“記憶”依然有效。

再看最後一個例子:

def test(num):
    in_num = num
    def nested(label):
        nonlocal in_num
        in_num += 1
        print(label, in_num)
    return nested

F = test(0)
F('a')
F('b')
F('c')

G = test(100)
G('mm')

1
2
3
mm 101
複製程式碼

多次呼叫工廠函式返回的不同內嵌函式副本F和G,彼此間的內嵌變數in_num是彼此獨立隔離的。

【妹子說】這個內嵌作用域以及閉包的特性確實非常特別,確實很pythonic啊!

公眾號二維碼:python資料科學家:

給妹子講python-S01E19解析Python內嵌作用域與函式閉包

相關文章