Python和Lua的默認做用域以及閉包

 

默認做用域

前段時間學了下Lua,發現Lua的默認做用域和Python是相反的。Lua定義變量時默認變量的做用域是全局(global,這樣說不是很準確,Lua在執行x = 1這樣的語句時會從當前環境開始一層層往上查找x,只有在找不到x的狀況下才定義全局變量)的,而Python定義變量時默認變量的做用域是局部(local)的(當前塊)。另外,Lua能夠再定義變量時在變量前加上local關鍵字來定義局部變量,而Python沒有相似的關鍵字,Python的變量只能定義在當前塊中。html

咱們知道,全局變量是很差的,而局部變量是好的,寫程序應該儘可能使用局部變量。因此一開始時我以爲Python的這種約定比較好,它的優勢就是能夠少打些字。寫Lua程序時不斷在心底默唸「勿忘local,勿忘local」,然而仍是有時會出現幾個漏網之魚並引起了一些神奇的bug。閉包

閉包

第一次意識到Python默認做用域的問題是在使用閉包時碰到的。關於閉包,Lua教程上有一段代碼:函數

function new_counter()
	local n = 0
	local function counter()
		n = n + 1
		return n
	end
	return counter
end

c1 = new_counter()
c2 = new_counter()
print(c1())  -- 打印1
print(c2())  -- 打印1
print(c1())  -- 打印2
print(c2())  -- 打印2

閉包的本質能夠參考SICP第三章的環境模型。在這裏能夠簡單的想象爲函數counter有一個私有成員n。htm

如今問題來了:我想用Python實現一樣功能的閉包?對象

首先直接從Lua代碼依葫蘆畫瓢改寫成Python代碼:教程

def new_counter():
	n = 0
	def counter():
		n = n + 1
		return n
	return counter

而後傻眼:這個程序不能運行,第4行訪問了未賦值的變量n。出錯的緣由並不是是Python不支持閉包,而是Python的賦值操做訪問不了上一層的變量n(實際上,Python認爲這是定義局部變量,而非賦值。在Python中定義局部變量與賦值操做在語法上是衝突的,Python乾脆只支持可重定義的定義語句)。因爲Python默認做用域是局部的,因此當程序運行到n = n + 1時,Python認爲這是一個變量定義操做,因而建立了一個(未初始化的)局部變量n——而且順利地覆蓋了new_counter這一層的n——而後試圖把n + 1賦值給n,可是n未初始化,n + 1無法計算,因此程序報錯。ip

能夠用個小技巧來實現閉包賦值的功能:作用域

def new_counter():
	n = [0]
	def counter():
		n[0] = n[0] + 1
		return n[0]
	return counter

這裏n[0] = n[0] + 1不會出錯的緣由是這裏的等號和前面n = n + 1的等號含義不同。n[0] = n[0] + 1中的等號意思是修改n的某個屬性。事實上這個等號最終調用了list的__setitem__方法。而n = n + 1中的等號意思是在當前環境將n + 1這個值綁定到符號n中(若是當前環境已存在符號n,就覆蓋它)。it

另外題外話:打死我不用這種寫法,多難看吶。反正Python是面嚮對象語言,要實現個計數器,大不了寫個類。io

定義與賦值的分離

先總結一下Python與Lua的默認做用域的特色:

一、 Lua默認做用域是全局的,寫程序時要牢記local關鍵字(除非確實要定義全局變量),不當心忘了local也不會提示,就等着糾bug吧。

二、 Python默認做用域是局部的,雖然寫程序的思惟負擔少些,可是喪失了對上層變量賦值的能力(能夠改,但會讓語言更混亂)。

看來兩種默認做用域都有問題?我的認爲,出現以上問題的緣由是:Python和Lua沒有實現定義和賦值的分離。在Python和Lua中,像x = 1這樣的語句既能夠表示定義,也能夠表示賦值。其實不僅是這兩種語言,其實不少高級語言都沒有實現定義和賦值的分離。定義和賦值二者功能上很像,可是它們本質上是有差異的。

下面以x = 1爲例解釋定義與賦值:

定義的意思是:在當前環境中註冊符號x,並初始化爲1。若是x已經存在,則報錯(不容許重定義)或者覆蓋(容許重定義)。

賦值的意思是:從當前環境開始,一層層往上找直到第一次找到符號x,把它的值修改爲1。若是找不到就報錯(變量不存在)。

如今咱們稍微修改一下Python來實現定義和賦值的分離:用「:=」表示定義,用「=」表示賦值。而後重寫那個不能運行的new_counter例子(Python中賦值操做和定義局部變量衝突,換句話說,Python其實沒有賦值操做,因此咱們只需簡單的把「=」全換成「:=」就好了),看看它錯在哪:

def new_counter():
	n := 0
	def counter():
		n := n + 1
		return n
	return counter

這個程序爲何是錯的就很明顯了。第4行咱們要的是賦值操做,而非定義操做。修改爲正確的寫法:

def new_counter():
	n := 0
	def counter():
		n = n + 1
		return n
	return counter

這樣就能正確運行了(前提是有修改版的Python解釋器XD)。

最後說一些Lua的狀況。Lua感受上就把定義和賦值的分離實現了一半。帶有local關鍵字的等號語句確定是定義了。問題是不帶local的等號語句。對於這種語句Lua是這樣作的:先試圖作賦值,若是賦值失敗(變量不存在),就在最外層環境(全局環境)定義變量。也就是說,不帶local的等號語句把定義和賦值混在一塊兒了。另外,若是實現了定義和賦值的分離,就不須要考慮默認做用域的問題了——定義所有是在當前環境下定義,都是定義局部變量。我實在想不出在一個函數體或者什麼塊中定義全局變量的好處。

相關文章
相關標籤/搜索