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

2021-06-16 23:32:45 字數 3435 閱讀 3196

在之前幾個月裡,我教一些不了解python的孩子來慢慢熟悉這門語言。漸漸地,我發現了一些幾乎所有python初學者都會犯的錯誤,所以我決定跟來跟大家分享我的建議。這個系列的每個部分都會關注不同的常見錯誤,描述如何產生這種錯誤的,並且提供解決的方法。本文是第二部分。

作用域

在這篇文章裡,我們來關注作用域在python被誤用的地方。通常,當我們定義了乙個全域性變數(好吧,我這樣說是因為講解的需要——全域性變數是不好的),我們用乙個函式訪問它們是能被python理解的:

bar = 42

def foo():

print bar

在這裡,我們在foo函式裡使用了全域性變數bar,然後它也如預想的能夠正常執行:

>>> foo()

42

這樣做很酷。通常,我們在使用了這個特性之後就想在所有的**裡用上它。如果像以下的例子中使用的話還是能夠正常執行的:

bar = [42]

def foo():

foo()

>>> print bar

[42, 0]

但是,如果我們把bar變一下呢:

>>> bar = 42

... def foo():

... bar = 0

... foo()

... print bar

42

我們可以看到foo函式執行的好好的並且沒有丟擲異常,但是當我們列印bar的值的時候會發現它的值仍然是42。造成這種情況的原因就是 bar=0 這行**,它沒有改變全域性變數bar的值,而是建立了乙個名字也叫bar的區域性變數並且它的值為0。這是個很難發現的bug,這會讓沒有真正理解python作用域的新手非常痛苦。為了理解python是如何處理區域性變數和全域性變數的,我們來看一種更少見的,但是可能會更讓人困惑的錯誤,我們在列印bar的值後定義乙個叫bar這個區域性變數:

bar = 42

def foo():

print bar

bar = 0

這樣寫應該是不會出錯的,不是嗎?我們在列印了值之後定義了相同名稱的變數,所以這應該是不會影響的(python畢竟是一種解釋型語言),真的是這樣嗎?

出錯了

這怎麼可能呢?好吧,這裡有兩處錯誤。第一點就是關於python的,作為一種解釋型語言(非常酷,我們都同意這一點),是一行一行地執行的。而事實上,python是乙個宣告乙個宣告執行的。為了讓你對我想表達的意思有點感覺,趕緊開啟你最愛的shell,然後輸入以下**:

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是否被賦值了。看看下面的例子:

bar = 42

def foo(baz):

if baz > 0:

print bar

bar = 0

python在動態和靜態之間玩了乙個微妙的遊戲。它唯一知道的事情就是bar是被賦值了,但它不知道在賦值前被引用這個異常是否存在直到它真的發生。好吧,老實說,它根本就不知道這個變數是否被賦值!

bar = 42

def foo():

print bar

if false:

bar = 0

>>> foo()

traceback (most recent call last):

file "", line 1, in foo()

file "", line 3, in foo

print bar

unboundlocalerror: local variable 'bar' referenced before assignment

看到上面的**裡面,雖然我們作為一種智慧型生物能夠很清楚的知道不會給bar賦值。python無視了那個事實而是仍然宣告了bar這個區域性變數。

關於這個問題我已經說了夠長了。我們需要的是解決方案,我會在這裡給出兩個解決方法。

>>> bar = 42

... def foo():

... global bar

... print bar

... bar = 0

...

... foo()

42>>> bar

0

第一就是使用global關鍵字。這是不言自明的。這會讓python知道bar是乙個全域性變數而不是區域性變數。

第二個方法,也是更推薦使用的,就是不要使用全域性變數。在我的大量python開發工作中從來沒有用到global這個關鍵字。能知道怎麼用它就行了,但最終還是要盡量避免使用它。如果你想儲存在**裡至始至終用到的值的時候,把它定義為乙個類的屬性。用這種方法的話就完全不需要用global了,當你要用這個值的時候,通過類的屬性來訪問就可以了:

>>> class baz(object):

... bar = 42

...

... def foo():

... print baz.bar # global

... bar = 0 # local

... baz.bar = 8 # global

... print bar

...

... foo()

... print baz.bar420

8

語言常犯錯誤積累 二

語言常犯錯誤積累 二 1 結構型別定義時忘記在右括號加分號 這主要是很多人在編寫復合語句太多而誤把結構類定義當成復合語句了,所以往往在定義完後忘記加分號結束 struct date 2 把結構名當作變數名來使用 這可能對於初學者而言是常犯的錯誤,在程式語言中所有型別都需要相應的變數來呼叫,如果這裡你...

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

在之前幾個月裡,我教一些不了解python的孩子來慢慢熟悉這門語言。漸漸地,我發現了一些幾乎所有python初學者都會犯的錯誤,所以我決定跟來跟大家分享我的建議。這個系列的每個部分都會關注不同的常見錯誤,描述如何產生這種錯誤的,並且提供解決的方法。用乙個可變的值作為預設值 這是乙個絕對值得放在第乙個...

專案二 第二部

1 使用vim編輯器配置網路 使用 setup 配置ip位址 service network restart 重新啟動網路服務 vim etc sysconfig network scripts ifcfg eth0 使用vim編輯器開啟網絡卡配置檔案 修改完相應的資訊後,按esc鍵,退出編輯模式,...