Python和Lua的預設作用域以及閉包

2022-04-02 18:53:47 字數 2967 閱讀 3726

前段時間學了下lua,發現lua的預設作用域和python是相反的。lua定義變數時預設變數的作用域是全域性(global,這樣說不是很準確,lua在執行x = 1這樣的語句時會從當前環境開始一層層往上查詢x,只有在找不到x的情況下才定義全域性變數)的,而python定義變數時預設變數的作用域是區域性(local)的(當前塊)。另外,lua可以再定義變數時在變數前加上local關鍵字來定義區域性變數,而python沒有類似的關鍵字,python的變數只能定義在當前塊中。

我們知道,全域性變數是不好的,而區域性變數是好的,寫程式應該盡量使用區域性變數。所以一開始時我覺得python的這種約定比較好,它的優點就是可以少打些字。寫lua程式時不斷在心底默念「勿忘local,勿忘local」,然而還是有時會出現幾個漏網之魚並引發了一些神奇的bug。

第一次意識到python預設作用域的問題是在使用閉包時碰到的。關於閉包,lua教程上有一段**:

function new_counter()

local n = 0

local function counter()

n = n + 1

return n

endreturn counter

endc1 = new_counter()

c2 = new_counter()

print(c1()) -- 列印1

print(c2()) -- 列印1

print(c1()) -- 列印2

print(c2()) -- 列印2

閉包的本質可以參考sicp第三章的環境模型。在這裡可以簡單的想象為函式counter有乙個私有成員n。

現在問題來了:我想用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沒法計算,所以程式報錯。

可以用個小技巧來實現閉包賦值的功能:

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,就覆蓋它)。

另外題外話:打死我不用這種寫法,多難看吶。反正python是物件導向語言,要實現個計數器,大不了寫個類。

先總結一下python與lua的預設作用域的特點:

1、 lua預設作用域是全域性的,寫程式時要牢記local關鍵字(除非確實要定義全域性變數),不小心忘了local也不會提示,就等著糾bug吧。

2、 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的等號語句把定義和賦值混在一起了。另外,如果實現了定義和賦值的分離,就不需要考慮預設作用域的問題了——定義全部是在當前環境下定義,都是定義區域性變數。我實在想不出在乙個函式體或者什麼塊中定義全域性變數的好處。

python函式預設引數作用域

當def函式引數預設值為物件時,例如列表,字典 示例1 猜測一下,會輸出什麼?def ddd a,b return b print ddd 1 print ddd 2,a b c print ddd 3 1 a b c 2 3 你是否認為是這樣?輸出一下看看 輸出結果 1 a b c 2 1,3 看...

函式的作用域和預設引數

一 預設引數 1 不指定,就使用預設引數 stanf student 不會報錯 2 沒有預設值得引數我可以叫做必填引數 3 預設引數可以定義多個 def stamf a,b 2,c 3 4 定義函式的時候一定是必選引數在前,預設引數在後 二 可變引數 儲存在乙個元組tuple 示例 a的平方 b的平...

python的預設引數 Python 預設引數的坑

話不多說,上 def fun x,a b c b b x print f a b c fun 1 fun 2 fun 3 我們在這段 中 定義了乙個位置引數 x,乙個乙個預設引數 a,a 的預設值為空列表,另乙個預設引數 b,b 的預設值為乙個空字串,在函式體中定義了乙個變數 c,並也給 c 傳遞了...