x = 3
y = [3]
def test1():
x += 1
print x
def test2():
y[0] = 4
y.append(5)
print y
test2()
test1()
这段代码执行结果: test2()成功打印[4, 5], test1()却报错: UnboundLocalError: local variable 'x' referenced before assignment
想不明白,为什么会这样,全局变量在函数里可以直接打印,但是如果要改变它的值,会报错,但是test2()为什么不报错?如果把y换成dict类型,就能在函数里不需要用global声明,可以直接改变y的值,但如果是str或number,就会报错,为什么?
PHP中文网2017-04-18 09:31:09
其實這個問題可以先放下變數參考的物件是可變還是不可變這件事情, 我們要注意的是變數的定位, 定義的時間點和作用域。
考慮以下程式碼:
def test(a):
print(a)
print(b)
test(1)
這段程式碼會引發一個錯誤:
1
Traceback (most recent call last):
File "tp.py", line 5, in <module>
test(1)
File "tp.py", line 3, in test
print(b)
NameError: name 'b' is not defined
a
是 function test
的 param variable, 所以它屬於一個 local variable, 他的作用域是 test
函數, 我們將 1 傳進去讓 a 參考, 所以 print(a)
沒有什麼大問題。
但是 b
從頭到尾都沒定義, 即使依據 LEGB 原則去尋找也遍尋不著, 所以 raise 了一個 NameError
。
要解決這個問題, 也許我們可以定義一個 global variable:
b = 100
def test(a):
print(a)
print(b)
test(1)
很好, 這次看起來沒什麼問題, 因為 Python 在 global 的作用域中找到了 b
, Python 之所以會使用 global b
那是因為在 local 我們並沒有定義 b
。
接著我們開始在函數裡面對變數賦值:
b = 100
def test(a):
b = 20
print(a)
print(b)
test(1)
print(b)
結果:
1
20
100
函數內的兩個print
不令人意外地印出了1 跟20, 但是為什麼離開function 之後印出b
的值是100 呢?
因為我們在函數中寫了這樣一個print
不令人意外地印出了 1 跟 20, 但是為什麼離開 function 之後印出 b
的值是 100 呢?
因為我們在函數中寫了這樣一個 賦值 兼 定義 b = 20
, 所以在 test
中看到的 b
都是 local 的, 不是 global 的, 所以我們對 b
造成的任何更動都不會影響到 global b
賦值
定義
b = 20
, 所以在test
中看到的b
都是local 的, 不是global 的, 所以我們對b
造成的任何更動都不會影響到global b
。 這告訴我們:
當 local variable 沒有被定義時, Python 會自動去使用 global variable, 反之則不會global
那我們要怎麼樣才能在函數內對 global 的變數進行操作呢? 這就需要 global
關鍵字 對 b
做了說明, Python 會將 test
中的 b
當作是要存取 global 的變量 b
而 b = 20
這個關鍵字的輔助了:
b = 100
def test(a):
global b
b = 20
print(a)
print(b)
test(1)
print(b)
1
20
20
有了
關鍵字對b
做了說明, Python 會將test
中的b
當作是要存取global 的變數< code>b 而b = 20
只會被當作一個賦值動作而不會定義一個新的local variable。 接著讓我們來看一個比較令人疑惑的例子:
b = 100
def test(a):
print(a)
print(b)
b = 20
test(1)
print(b)
UnboundLocalError
, 為什麼會這樣呢? 原因很簡單, b
在這個函數內出現了賦值兼定義的動作: b = 20
, 所以 test
內的 b
都是 local 的(在這裡並沒有使用 global 來指明 b
是 global 的), 所以當 print(b)
的時候, Python 會試圖去抓 local b
而不是 global b
, 但是悲劇的是, 在這一步, local b
還沒被賦值, 所以才會說 local variable 'b' referenced before assignment
結果:
1
Traceback (most recent call last):
File "tp.py", line 8, in <module>
test(1)
File "tp.py", line 5, in test
print(b)
UnboundLocalError: local variable 'b' referenced before assignment
這邊出現了
在被賦值前被參考了, 那自然是不行的了。 🎜 🎜回頭來看你給的例子:🎜x = 3
def test1():
x += 1
print x
在這裡, test1
中有 x
的定義發生 (沒有 global, 且 x
出現在等號左邊), 自然在參考 x
的時候, 會想要取用 local 的, 但是因為:
x += 1 等義 x = x + 1
所以在等號右邊想要取得 x
參考的值時, 卻發現他還沒被賦值呢! 那這就跟上面一個例子一樣:
UnboundLocalError: local variable 'x' referenced before assignment
至於你的 test2
不會出現問題:
y = [3]
def test2():
y[0] = 4
y.append(5)
print y
是因為 y 沒有在 test2
中出現在等號左邊, 所以 Python 自動認定使用 global 的 y
中出現在等號左邊, 所以 Python 自動認定使用 global 的 y
那自然是沒問題了!
觀察一個 function 內有沒有定義 local vairable 就看該 variable name 有沒有出現在等號左邊, 且該 variable name 沒有被 global
指明
若有定義 local variable 則 Python 將不會去尋找 global variable, 否則 Python 會自動取用 global vairable
如果有定義 local vairable 但是你在該 variable 被賦值前就去讀取參考的話, 就會有 UnboundLocalError 出現
要解決這個問題, 請記得加註 global
指明全局, 否則不該有這種寫法出現 (in-place 操作, 或是後面才賦值)
類似的問題也會出現在 local function 中, 解決的辦法是加註 nonlocal
(Python3), 不過那又是另一個故事了。
我回答過的問題: Python-QA
PHP中文网2017-04-18 09:31:09
python 的變數作用域是這樣規定的,test1中可以 print x
但是卻不能修改 x
有人說 “局部作用域中全域變數應是唯讀” ,但是對於引用型變數又不滿足這個條件。 。 。
python 的這個地方確實繞人
天蓬老师2017-04-18 09:31:09
簡單的說,局部作用域中不能改變全域變數的綁定,在 CPython
也就是不能改變變數的位址。
參考:Python程式設計師最常犯的十個錯誤 中的 常見錯誤4:錯誤理解Python中的變數名稱解析
伊谢尔伦2017-04-18 09:31:09
你的test1
函数有赋值操作x += 1
, 被Python解释器认为是函数本地作用域的变量, 但是该变量x
在赋值之前并没有被定义, 而且x
是number
, 属于不可变类型, 针对不可变类型的赋新值, 需要重新创建一个不可变类型的对象,并将原来的变量重新指向新创建的对象, 但是这个新创建的对象在LEGB
中都沒有找到, 所以會報錯
test2
函数中, y
是个list
, 屬於可變類型, list在append之後,還是指向同個內存地址,因為list是可變類型,可以在原處修改, 所以並不會報錯
Python
中所有赋值操作基本都分为下面三个步骤(以a = 3
為例):
建立一個物件來代表該值3
創建一個變數a
, 如果他還沒被創建的話
將變數a
与新的对象3
與新的物件
test2
函数在执行过程中变量y
我們觀察下你這
y = [3]
print(id(y))
def test2():
y[0] = 4
print(id(y))
y.append(5)
print(id(y))
print(y)
test2()
x = 3
print(id(x))
x = 4
print(id(x))
輸出
79051032
79051032
79051032
[4, 5]
1375754544
1375754560
y
其实是在原地修改的, 所以不需要重复创建变量; 而对于x
不可变类型来说, 要给他赋值, 就必须先创建一个新的对象, 即使名字一样, 可以看到其实如果每次不同的赋值, 实际上x
的内存地址并不相同, 这也就可以解释str
或number
可以看到都會報錯了🎜