1. 程式人生 > >Python:閉包

Python:閉包

-i ons unbound height 只讀 變量 pri console 獲得

閉包(Closure)

在計算機科學中,閉包(英語:Closure),又稱詞法閉包(Lexical Closure)或函數閉包(function closures),是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即使已經離開了創造它的環境也不例外。

命名空間與作用域

我們可以把命名空間看做一個大型的字典類型(Dict),裏面包含了所有變量的名字和值的映射關系。在 Python 中,作用域實際上可以看做是“在當前上下文的位置,獲取命名空間變量的規則”。在 Python 代碼執行的任意位置,都至少存在三層嵌套的作用域:

  • local 最內層作用域,最早搜索,包含所有局部變量**(Python 默認所有變量聲明均為局部變量)**
  • non-local 所有包含當前上下文的外層函數的作用域,由內而外依次搜索,這裏包含的是非局部也非全局的變量
  • global 一直向上搜索,直到當前模塊的全局變量
  • built-in 最外層,最後搜索的,內置(built-in)變量

在任意執行位置,可以將作用域看成是對下面這樣一個命名空間的搜索:


scopes =
{ "local": {"locals": None, "non-local": {"locals": None, "global": {"locals": None, "built-in": ["built-ins"]}}}, }
 

除了默認的局部變量聲明方式,Python 還有globalnonlocal兩種類型的聲明(nonlocal是Python 3.x之後才有,2.7沒有),其中 global 指定的變量直接指向(3)當前模塊的全局變量,nonlocal則指向(2)最內層之外,global以內的變量。這裏需要強調指向(references and assignments)的原因是,普通的局部變量對最內層局部作用域之外只有**只讀(read-only)**的訪問權限,比如下面的例子:

>>>x = 100
>>def main():
    x += 1
    print
(x) >>>main() Traceback (most recent call last): File "<pyshell#50>", line 1, in <module> main() File "<pyshell#49>", line 2, in main x += 1 UnboundLocalError: local variable x referenced before assignment

這裏拋出UnboundLocalError,是因為main()函數內部的作用域對於全局變量x僅有只讀權限,想要在main()中對x進行改變,不會影響全局變量,而是會創建一個新的局部變量,顯然無法對還未創建的局部變量直接使用x += 1。如果想要獲得全局變量的完全引用,則需要global聲明:

>>>x = 100
>>>def main():
    global x
    x += 1
    print(x)
    
>>>main()
print(x) # 全局變量已被改變
101

Python閉包練習

到這裏基本上已經了解了 Python 作用域的規則,我們仿照 JavaScript 寫一個計數器的閉包:

"""
/* JavaScript Closure example */
var inc = function(){  
  var x = 0;
  return function(){
    console.log(x++);
  };
};
var inc1 = inc()
var inc2 = inc()
"""
# Python 3.6
def inc():
    x = 0
    def inner():
        nonlocal x
        x += 1
        print(x)
    return inner
inc1 = inc()
inc2 = inc()

inc1()
inc1()
inc1()
inc2()
1
2
3
1

上面的例子中,inc1()是在全局環境下執行的,雖然全局環境是不能向下獲取到inc()中的局部變量x的,但是我們返回了一個inc()內部的函數inner(),而inner()inc()中的局部變量是有訪問權限的。也就是說inner()inc()內的局部作用域打包送給了inc1inc2,從而使它們各自獨立擁有了一塊封閉起來的作用域,不受全局變量或者任何其它運行環境的影響,因此稱為閉包。

閉包函數都有一個__closure__屬性,其中包含了它所引用的上層作用域中的變量:

print(inc1.__closure__[0].cell_contents)
print(inc2.__closure__[0].cell_contents)
[3]
[1]

原文出處: rainyear

參考

  1. 9.2. Python Scopes and Namespaces
  2. Visualize Python Execution
  3. Wikipedia::Closure

Python:閉包