Python:閉包
閉包(Closure)
在計算機科學中,閉包(英語:Closure),又稱詞法閉包(Lexical Closure)或函數閉包(function closures),是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即使已經離開了創造它的環境也不例外。
命名空間與作用域
我們可以把命名空間看做一個大型的字典類型(Dict),裏面包含了所有變量的名字和值的映射關系。在 Python 中,作用域實際上可以看做是“在當前上下文的位置,獲取命名空間變量的規則”。在 Python 代碼執行的任意位置,都至少存在三層嵌套的作用域:
- local 最內層作用域,最早搜索,包含所有局部變量**(Python 默認所有變量聲明均為局部變量)**
- non-local 所有包含當前上下文的外層函數的作用域,由內而外依次搜索,這裏包含的是非局部也非全局的變量
- global 一直向上搜索,直到當前模塊的全局變量
- built-in 最外層,最後搜索的,內置(built-in)變量
在任意執行位置,可以將作用域看成是對下面這樣一個命名空間的搜索:
scopes =
|
除了默認的局部變量聲明方式,Python 還有global
和nonlocal
兩種類型的聲明(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()
內的局部作用域打包送給了inc1
和inc2
,從而使它們各自獨立擁有了一塊封閉起來的作用域,不受全局變量或者任何其它運行環境的影響,因此稱為閉包。
閉包函數都有一個__closure__
屬性,其中包含了它所引用的上層作用域中的變量:
print(inc1.__closure__[0].cell_contents) print(inc2.__closure__[0].cell_contents) [3] [1]
原文出處: rainyear
參考
- 9.2. Python Scopes and Namespaces
- Visualize Python Execution
- Wikipedia::Closure
Python:閉包