Python 新手常犯錯誤(第二部分)

轉發自: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
相關文章
相關標籤/搜索