轉發自:http://blog.jobbole.com/43826/python
在以前幾個月裏,我教一些不瞭解Python的孩子來慢慢熟悉這門語言。漸漸地,我發現了一些幾乎全部Python初學者都會犯的錯誤,因此我決定跟來跟你們分享個人建議。這個系列的每一個部分都會關注不一樣的常見錯誤,描述如何產生這種錯誤的,而且提供解決的方法。本文是第二部分。shell
做用域app
在這篇文章裏,咱們來關注做用域在Python被誤用的地方。一般,當咱們定義了一個全局變量(好吧,我這樣說是由於講解的須要——全局變量是很差的),咱們用一個函數訪問它們是能被Python理解的:函數
1
2
3
|
bar
=
42
def
foo():
print
bar
|
在這裏,咱們在foo函數裏使用了全局變量bar,而後它也如預想的可以正常運行:spa
1
2
|
>>> foo()
42
|
這樣作很酷。一般,咱們在使用了這個特性以後就想在全部的代碼裏用上它。若是像如下的例子中使用的話仍是可以正常運行的:code
1
2
3
4
5
6
7
|
bar
=
[
42
]
def
foo():
bar.append(
0
)
foo()
>>>
print
bar
[
42
,
0
]
|
可是,若是咱們把bar變一下呢:對象
1
2
3
4
5
6
|
>>> bar
=
42
...
def
foo():
... bar
=
0
... foo()
...
print
bar
42
|
咱們能夠看到foo函數運行的好好的而且沒有拋出異常,可是當咱們打印bar的值的時候會發現它的值仍然是42。形成這種狀況的緣由就是 bar=0 這行代碼,它沒有改變全局變量bar的值,而是建立了一個名字也叫bar的局部變量而且它的值爲0。這是個很難發現的bug,這會讓沒有真正理解Python做用域的新手很是痛苦。爲了理解Python是如何處理局部變量和全局變量的,咱們來看一種更少見的,可是可能會更讓人困惑的錯誤,咱們在打印bar的值後定義一個叫bar這個局部變量:blog
1
2
3
4
|
bar
=
42
def
foo():
print
bar
bar
=
0
|
這樣寫應該是不會出錯的,不是嗎?咱們在打印了值以後定義了相同名稱的變量,因此這應該是不會影響的(Python畢竟是一種解釋型語言),真的是這樣嗎?遊戲
出錯了ci
這怎麼可能呢?好吧,這裏有兩處錯誤。第一點就是關於Python的,做爲一種解釋型語言(很是酷,咱們都贊成這一點),是一行一行地執行的。而事實上,Python是一個聲明一個聲明執行的。爲了讓你對我想表達的意思有點感受,趕忙打開你最愛的shell,而後輸入如下代碼:
1
|
def
foo():
|
按回車鍵。正如你看到的,shell裏面並無打出任何輸出而是等着讓你繼續函數的定義。Shell裏會一直這樣直到你中止定義函數。這是由於定義函數是一個聲明。好吧,這是一個混合的聲明,裏面包含了一些其餘的聲明,但它仍然是一個聲明。直到函數被調用,否則這個函數裏的內容是不會執行的。真正執行的是一個function類型的對象被建立出來了。
這引導咱們來關注第二點。再強調一下,Python的動態性和解釋型的特性讓咱們相信當 print bar 這行被執行的時候,Python會在首先在局部做用域裏尋找叫bar的變量而後再去尋找全局做用域裏的。但實際上發生的是局部做用域不是徹底動態的。當def 這個聲明執行的時候,Python會靜態地從這個函數的局部做用域裏獲取信息。當來到 bar=0 這行的時候(不是執行到這行代碼,而是當Python解釋器讀到這行代碼的時候),它會把’bar’這個變量加入到foo函數的局部變量列表裏。當foo函數執行而且Python準備執行print bar這行的時候,它就會在局部的做用域裏尋找這個變量,因爲這個過程是靜態的,Python知道這個變量尚未被賦值,這個變量沒有值,因此拋出了異常。
你可能會問:爲何不能在聲明函數的時候拋出這個異常呢?Python能夠知道預先知道bar這個變量在賦值前被引用了。這個問題的答案就是Python沒法知道這個局部變量bar是否被賦值了。看看下面的例子:
1
2
3
4
5
|
bar
=
42
def
foo(baz):
if
baz >
0
:
print
bar
bar
=
0
|
Python在動態和靜態之間玩了一個微妙的遊戲。它惟一知道的事情就是bar是被賦值了,但它不知道在賦值前被引用這個異常是否存在直到它真的發生。好吧,老實說,它根本就不知道這個變量是否被賦值!
1
2
3
4
5
6
7
8
9
10
11
12
13
|
bar
=
42
def
foo():
print
bar
if
False
:
bar
=
0
>>> foo()
Traceback (most recent call last):
File
"<pyshell#17>"
, line
1
,
in
<module>
foo()
File
"<pyshell#16>"
, line
3
,
in
foo
print
bar
UnboundLocalError: local variable
'bar'
referenced before assignment
|
看到上面的代碼裏面,雖然咱們做爲一種智能生物可以很清楚的知道不會給bar賦值。Python無視了那個事實而是仍然聲明瞭bar這個局部變量。
關於這個問題我已經說了夠長了。咱們須要的是解決方案,我會在這裏給出兩個解決方法。
1
2
3
4
5
6
7
8
9
10
|
>>> bar
=
42
...
def
foo():
...
global
bar
...
print
bar
... bar
=
0
...
... foo()
42
>>> bar
0
|
第一就是使用global關鍵字。這是不言自明的。這會讓Python知道bar是一個全局變量而不是局部變量。
第二個方法,也是更推薦使用的,就是不要使用全局變量。在個人大量Python開發工做中歷來沒有用到global這個關鍵字。能知道怎麼用它就好了,但最終仍是要儘可能避免使用它。若是你想保存在代碼裏至始至終用到的值的時候,把它定義爲一個類的屬性。用這種方法的話就徹底不須要用global了,當你要用這個值的時候,經過類的屬性來訪問就能夠了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
>>>
class
Baz(
object
):
... bar
=
42
...
...
def
foo():
...
print
Baz.bar
# global
... bar
=
0
# local
... Baz.bar
=
8
# global
...
print
bar
...
... foo()
...
print
Baz.bar
42
0
8
|