致命錯誤!Python開發(fā)者的7個崩潰瞬間
本文轉(zhuǎn)載自公眾號“讀芯術(shù)”(ID:AI_Discovery)。
毫無疑問,Python是當(dāng)今使用最為廣泛的編程語言。它的語法簡單且易讀,也很容易上手。
但無論你經(jīng)驗多豐富,或是已使用過多少種語言,切換到Python時都不能保證非常順利。具有面向?qū)ο缶幊瘫尘暗拈_發(fā)人員容易忽略Python的慣用特性,很可能會濫用編程結(jié)構(gòu),從而產(chǎn)生不可預(yù)見且很難捕捉的錯誤。更糟糕的是,大多數(shù)錯誤很難發(fā)現(xiàn),可能在后續(xù)工作中造成麻煩。
下文匯總了程序員(尤其是新手)可能犯的常見錯誤,以及該如何糾正這些錯誤,編寫更好的、無錯誤的Python代碼。讓我們開始吧!
編寫過于風(fēng)格化的代碼
這是Python初學(xué)者的一個典型特征。為了編寫類似高級偽英語的代碼,他們最終在其代碼庫中添加了以下類型的代碼段:
- if x == 1 or x == 2
看起來似乎不錯。這行代碼的意思是變量x必須為1或2才能滿足條件。但是,此類代碼片段太過風(fēng)格化,影響了可讀性。下面的替代代碼段很容易理解,該行代碼檢查值是否屬于列表中的元素:
- if x in [1,2]
不必要的比較運算符:None和零
具有Java背景的程序員知道需要進(jìn)行多少次空值(null)檢查(尤其是在Java 8之前的版本中)。因此,在Python中看到這樣的比較運算符就不足為奇了:
- a == None b != None
上述情況可以利用python的方式編寫代碼來增強(qiáng)可讀性:
- a is None
- b is not None
同樣值得注意的是,對于0,實際上并不需要在條件邏輯中使用比較運算符。0解釋為false,而非零數(shù)字則視為true。
使用長鏈?zhǔn)綏l件位邏輯
在大多數(shù)語言(包括Swift,Java,Kotlin)中,可用以下方式編寫某些比較邏輯:
- if a < b < c
大多數(shù)語言不能在非關(guān)聯(lián)優(yōu)先級中使用相鄰運算符,而Python則不同,Python可以鏈?zhǔn)劫x值,如以下代碼所示:
- if a < b < c
因此,這樣做可以避免按位運算符。
使用type()代替isinstance(),反之亦然
type和isinstance是Python中用于類型檢查的兩個廣泛使用的內(nèi)置函數(shù)。通常,新手開發(fā)人員會認(rèn)為這兩個函數(shù)很相似并互換使用。這可能引發(fā)無法預(yù)料的錯誤,因為type()和isinstance()具有一些細(xì)微的差異。
isinstance()函數(shù)用于檢查對象是否是指定類的實例,同時還要注意繼承。另一方面,type()僅檢查引用類型是否相等,并丟棄子類型。因此,以下代碼使用type()和isinstance()給出了不同的結(jié)果:
- class Vehicle:
- pass
- class Car(Vehicle):
- passisinstance(Car(), Vehicle) #returns True
- type(Car()) == Vehicle # returns False
同樣,以下代碼將布爾值視為int的實例(因為True和False基本上被視為1和0),但是使用type函數(shù)給出了不同的結(jié)果。
- type(True) == int # falseisinstance(True, int) # trueisinstance(False,int) # true
因此,重要的是要了解Python的兩個類型檢查器函數(shù)之間的差異,并且不要彼此混淆。
混淆作用域中的局部變量和全局變量
Python中的作用域規(guī)則看起來相當(dāng)簡單,但很容易造成誤解。例如,以下代碼在函數(shù)內(nèi)部使用全局變量:
- a = 10
- def printMe():
- print(a)printMe() # prints 10
如果通過修改函數(shù)中的變量來稍微調(diào)整上述代碼,就會拋出錯誤:
- a = 20
- def printA():
- print(a)
- a = 10print(a) # gives 20
- printA() # gives error as a is referenced before assigned
一旦在函數(shù)內(nèi)部修改了全局變量,Python就會將其視為局部變量,從而覆蓋全局變量。甚至賦值前的打印語句也沒有執(zhí)行。
為確保此類名稱沖突不會導(dǎo)致錯誤,可以在局部函數(shù)內(nèi)為全局變量附加global關(guān)鍵字。甚至最好將全局變量(如果確實需要使用)放在單獨的類中,以便始終將全局變量與類名一起使用。
可變默認(rèn)參數(shù)
在Python中,使用默認(rèn)參數(shù)很常見,它可以避免在調(diào)用函數(shù)時出現(xiàn)一長串參數(shù)。列表、字典和集合是Python中的可變類型。設(shè)置默認(rèn)值會導(dǎo)致意外結(jié)果,如下所示:
- def addToList(x, a=[]):
- a.append(x)
- return alistOne = addToList(5)
- #prints [5]anotherList = addToList(10)
- # [5, 10]
如你所見,第二個列表包含先前添加的元素,因為函數(shù)中的可變默認(rèn)參數(shù)將它們存儲在各個狀態(tài)之間。
Python中可變默認(rèn)對象的問題表現(xiàn)在定義函數(shù)時會對其進(jìn)行評估,這會導(dǎo)致可變值也保存先前的內(nèi)容。為避免此類嚴(yán)重的錯誤,請將None設(shè)置為默認(rèn)值,然后在函數(shù)內(nèi)分配可變變量,如下所示:
- def addElement(x, a=None):
- if not a:
- a = []
- a.append(x)
- return a
忽略多重繼承和方法解析順序
圖源:unsplash
與大多數(shù)語言不同,Python支持多重繼承。即在具有繼承的類中,方法和類變量將根據(jù)繼承類時指定的順序執(zhí)行。初學(xué)者通常會忽略此概念,尤其是在僅使用單一繼承的情況下。在下面的代碼中,當(dāng)調(diào)用C類的方法時,將使用超類B的相應(yīng)方法:
- >>> class A(object):
- ... def me(self):
- print("class A")
- >>> class B(A):
- ... def me(self):
- print("class B")
- class C(B, A):
- passc = C()
- c.me() # prints class B
Python中繼承類的順序很重要,它可用來解決這些問題。
Python雖簡單,但小心不要與其他語言混淆了,這可能會導(dǎo)致奇怪的錯誤和程序崩潰。希望上述的總結(jié)可以幫你理清概念,編寫更穩(wěn)定的Python代碼。