python的作用域问题
高洛峰
高洛峰 2017-04-18 09:29:09
0
4
752
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,就会报错,为什么?

高洛峰
高洛峰

拥有18年软件开发和IT教学经验。曾任多家上市公司技术总监、架构师、项目经理、高级软件工程师等职务。 网络人气名人讲师,...

全部回复(4)
洪涛

其实这个问题可以先放下变量参考的对象是可变还是不可变这件事情, 我们要注意的是变量的定位, 定义的时间点和作用域。

考虑以下代码:

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 了一个 NameErrora 是 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 呢?
因為我們在函數中寫了這樣一個 賦值 定義 b = 20, 所以在 test 中看到的 b 都是 local 的, 不是 global 的, 所以我們對 b 造成的任何更動都不會影響到 global b

這告訴我們:

當 local variable 沒有被定義時, Python 會自動去使用 global variable, 反之則不會

那我們要怎麼樣才能在函數內對 global 的變量進行操作呢? 這就需要 global 這個關鍵字的輔助了:

b = 100

def test(a):
    global b
    b = 20
    print(a)
    print(b)
    
test(1)
print(b)
1
20
20

有了 global 關鍵字 對 b 做了說明, Python 會將 test 中的 b 當作是要存取 global 的變量 bb = 20 僅會被當作一個賦值動作而不會定義一個新的 local variable。

接著讓我們來看一個比較令人疑惑的例子:

b = 100

def test(a):
    print(a)
    print(b)
    b = 20
    
test(1)
print(b)

結果:

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

這邊出現了 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

要解决这个问题, 也许我们可以定义一个 global variable:

x = 3

def test1():
    x += 1
    print x
很好, 这次看起来没什么问题, 因为Python 在global 的作用域中找到了b, Python 之所以会使用global b 那是因为在local 我们并没有定义b。 🎜 🎜接着我们开始在函数里面对变量赋值:🎜
x += 1 等義 x = x + 1
🎜结果:🎜
UnboundLocalError: local variable 'x' referenced before assignment
🎜函数内的两个print 不令人意外地印出了1 跟20, 但是为什么离开function 之后印出b 的值是100 呢?
因为我们在函数中写了这样一个🎜赋值🎜 兼🎜定义🎜 b = 20, 所以在test 中看到的b 都是local 的, 不是global 的, 所以我们对b 造成的任何更动都不会影响到global b。 🎜 🎜这告诉我们:🎜
🎜当 local variable 没有被定义时, Python 会自动去使用 global variable, 反之则不会🎜
🎜那我们要怎么样才能在函数内对 global 的变量进行操作呢? 这就需要 global 这个关键字的辅助了:🎜
y = [3]

def test2():
    y[0] = 4
    y.append(5)
    print y
rrreee 🎜有了global 关键字对b 做了说明, Python 会将test 中的b 当作是要存取global 的变量bb = 20 仅会被当作一个赋值动作而不会定义一个新的local variable。 🎜 🎜接着让我们来看一个比较令人疑惑的例子:🎜 rrreee 🎜结果:🎜 rrreee 🎜这边出现了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 在被赋值前被参考了, 那自然是不行的了。 🎜 🎜回过头来看你给的例子:🎜rrreee

在这里, test1 中有x 的定义发生(没有global, 且x 出现在等号左边), 自然在参考x 的时候, 会想要取用local 的, 但是因为:test1 中有 x 的定義發生 (沒有 global, 且 x 出現在等號左邊), 自然在參考 x 的時候, 會想要取用 local 的, 但是因為:

rrreee

所以在等號右邊想要取得 x 參考的值時, 卻發現他還沒被賦值呢! 那這就跟上面一個例子一樣:

rrreee

至於你的 test2 不會出現問題:

rrreee

是因為 y 沒有在 test2 中出現在等號左邊, 所以 Python 自動認定使用 global 的 y 那自然是沒問題了!

結論

  1. 觀察一個 function 內有沒有定義 local vairable 就看該 variable name 有沒有出現在等號左邊, 且該 variable name 沒有被 global 指明

  2. 若有定義 local variable 則 Python 將不會去尋找 global variable, 否則 Python 會自動取用 global vairable

  3. 如果有定義 local vairable 但是你在該 variable 被賦值前就去讀取參考的話, 就會有 UnboundLocalError 出現

  4. 要解決這個問題, 請記得加註 global 指明全域, 否則不該有這種寫法出現 (in-place 操作, 或是後面才賦值)

  5. 類似的問題也會出現在 local function 中, 解決的辦法是加註 nonlocal rrreee

    所以在等号右边想要取得 x 参考的值时, 却发现他还没被赋值呢! 那这就跟上面一个例子一样:
  6. rrreee
至于你的 test2 不会出现问题:
rrreee

是因为 y 没有在 test2 中出现在等号左边, 所以 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🎜
    洪涛

    python 的变量作用域是这样规定的,test1中可以 print x 但是却不能修改 x
    有人说 “局部作用域中全局变量应是只读” ,但是对于引用型变量又不满足这个条件。。。

    python 的这个地方确实绕人

    Peter_Zhu

    简单的说,局部作用域中不能改变全局变量的绑定,在 CPython 也就是不能改变变量的地址。

    参看:Python程序员最常犯的十个错误 中的 常见错误4:错误理解Python中的变量名解析

    伊谢尔伦

    你的test1函数有赋值操作x += 1, 被Python解释器认为是函数本地作用域的变量, 但是该变量x在赋值之前并没有被定义, 而且xnumber, 属于不可变类型, 针对不可变类型的赋新值, 需要重新创建一个不可变类型的对象,并将原来的变量重新指向新创建的对象, 但是这个新创建的对象在LEGB中都没有找到, 所以会报错test1函数有赋值操作x += 1, 被Python解释器认为是函数本地作用域的变量, 但是该变量x在赋值之前并没有被定义, 而且xnumber, 属于不可变类型, 针对不可变类型的赋新值, 需要重新创建一个不可变类型的对象,并将原来的变量重新指向新创建的对象, 但是这个新创建的对象在LEGB中都没有找到, 所以会报错

    test2函数中, y是个list, 属于可变类型, list在append之后,还是指向同个内存地址,因为list是可变类型,可以在原处修改, 所以并不会报错

    Python中所有赋值操作基本都分为下面三个步骤(以a = 3为例):

    1. 创建一个对象来代表该值3

    2. 创建一个变量a, 如果他还没被创建的话

    3. 将变量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的内存地址并不相同, 这也就可以解释strnumber

    test2函数中, y是个list, 属于可变类型, list在append之后,还是指向同个内存地址,因为list是可变类型,可以在原处修改, 所以并不会报错🎜 🎜Python中所有赋值操作基本都分为下面三个步骤(以a = 3为例):🎜
    1. 🎜创建一个对象来代表该值3🎜
    2. 🎜创建一个变量a, 如果他还没被创建的话🎜
    3. 🎜将变量a与新的对象3相连接🎜
    🎜我们观察下你这test2函数在执行过程中变量y的内存地址变化🎜 rrreee 🎜输出🎜 rrreee 🎜可以看到y其实是在原地修改的, 所以不需要重复创建变量; 而对于x不可变类型来说, 要给他赋值, 就必须先创建一个新的对象, 即使名字一样, 可以看到其实如果每次不同的赋值, 实际上x的内存地址并不相同, 这也就可以解释strnumber都会报错了🎜
    热门教程
    更多>
    最新下载
    更多>
    网站特效
    网站源码
    网站素材
    前端模板