閉包(closure)是函數式編程的重要的語法結構,Python也支持這一特性,下面就開始介紹Python中的閉包。
首先看看閉包的概念:閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即使已經離開了創造它的環境也不例外。所以,閉包是由函數和與其相關的引用環境組合而成的實體。
在開始介紹閉包之前先看看Python中的namespace。
Python中的namespace
Python中通過提供 namespace 來實現重名函數/方法、變量等信息的識別,其一共有三種 namespace,分別爲:
- local namespace: 作用範圍爲當前函數或者類方法
- global namespace: 作用範圍爲當前模塊
- build-in namespace: 作用範圍爲所有模塊
當函數/方法、變量等信息發生重名時,Python會按照 “local namespace -> global namespace -> build-in namespace”的順序搜索用戶所需元素,並且以第一個找到此元素的 namespace 爲準。
同時,Python中的內建函數locals()和globals()可以用來查看不同namespace中定義的元素。
看一個例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | s = "string in global" num = 99 def numFunc(a, b): num = 100 print "print s in numFunc: ", s def addFunc(a, b): s = "string in addFunc" print "print s in addFunc: ", s print "print num in addFunc: ", num print "locals of addFunc: ", locals() print return "%d + %d = %d" %(a, b, a + b) print "locals of numFunc: ", locals() print return addFunc(a, b) numFunc(3, 6) print "globals: ", globals() |
上面代碼的結果爲:
代碼中通過locals()和globals()獲取了不同namespace中定義的元素,當在”numFunc”函數中訪問s變量的時候,由於local namespace中沒有定義s,所以會找到global中的s;但是,由於”addFunc”的local namespace中有s,所以可以直接使用。
Python創建閉包
下面通過一個例子一步一步分析一下Python如何實現閉包。
一個閉包的例子
下面看一個閉包的簡單的例子,例子中定義了一個可以配置打招呼的函數”greeting_conf”,在這個函數中,內嵌了一個”greeting”的內嵌函數:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | def greeting_conf(prefix): def greeting(name): print prefix, name return greeting mGreeting = greeting_conf("Good Morning") mGreeting("Wilber") mGreeting("Will") print aGreeting = greeting_conf("Good Afternoon") aGreeting("Wilber") aGreeting("Will") |
代碼的結果爲:
上面的代碼有下面幾個注意點:
- “greeting”函數訪問了non-local的變量”prefix”,根據前面namespace的介紹,這點是完全合法的
- 變量”prefix”並沒有隨着函數的退出而銷燬,反而是生命週期得到了延長
下面看看爲什麼變量”prefix”生命週期得到了延長?
__closure__屬性
在Python中,函數對象有一個__closure__屬性,我們可以通過這個屬性看看閉包的一些細節。
1 2 3 4 5 6 7 8 9 10 11 | def greeting_conf(prefix): def greeting(name): print prefix, name return greeting mGreeting = greeting_conf("Good Morning") print dir(mGreeting) print mGreeting.__closure__ print type(mGreeting.__closure__[0]) print mGreeting.__closure__[0].cell_contents |
通過__closure__屬性看到,它對應了一個tuple,tuple的內部包含了cell類型的對象。
對於這個例子,可以得到cell的值(內容)爲”Good Morning”,也就是變量”prefix”的值。
從這裏可以看到閉包的原理,當內嵌函數引用了包含它的函數(enclosing function)中的變量後,這些變量會被保存在enclosing function的__closure__屬性中,成爲enclosing function本身的一部分;也就是說,這些變量的生命週期會和enclosing function一樣。
閉包和函數
閉包只是在表現形式上跟函數類似,但實際上不是函數。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | def greeting_conf(prefix): def greeting(name): print prefix, name return greeting mGreeting = greeting_conf("Good Morning") print "function name is:", mGreeting.__name__ print "id of mGreeting is:", id(mGreeting) print aGreeting = greeting_conf("Good Afternoon") print "function name is:", aGreeting.__name__ print "id of aGreeting is:", id(aGreeting) |
從代碼的結果中可以看到,閉包在運行時可以有多個實例,不同的引用環境(這裏就是prefix變量)和相同的函數(這裏就是greeting)組合可以產生不同的實例。
Python中怎麼創建閉包
在Python中創建一個閉包可以歸結爲以下三點:
- 閉包函數必須有內嵌函數
- 內嵌函數需要引用該嵌套函數上一級namespace中的變量
- 閉包函數必須返回內嵌函數
通過這三點,就可以創建一個閉包,是不是想到了上一篇中介紹的Python裝飾器。沒錯,Python裝飾器就是使用了閉包。
總結
本文介紹瞭如何通過Python創建一個閉包,以及Python創建的閉包是如何工作的。