摘要:Python是一門簡單易學(xué)的編程語言碎赢,語法簡潔而清晰,并且擁有豐富和強(qiáng)大的類庫速梗。在日常開發(fā)中肮塞,開發(fā)者很容犯一些低級的錯誤,本文總結(jié)了開發(fā)者最容易犯的10個錯誤姻锁。
小編推薦大家可以加我的扣扣群 735934841 峦嗤。里面有海量視頻教程和學(xué)習(xí)資料免費(fèi)領(lǐng)取,不失為是一個學(xué)習(xí)的好地方屋摔,歡迎你的到來烁设。一起交流學(xué)習(xí)!共同進(jìn)步5鍪浴装黑!
Python是一門簡單易學(xué)的編程語言,語法簡潔而清晰弓熏,并且擁有豐富和強(qiáng)大的類庫恋谭。與其它大多數(shù)程序設(shè)計(jì)語言使用大括號不一樣 ,它使用縮進(jìn)來定義語句塊挽鞠。
在平時(shí)的工作中疚颊,Python開發(fā)者很容易犯一些小錯誤,這些錯誤都很容易避免信认,本文總結(jié)了Python開發(fā)者最常犯的10個錯誤材义,一起來看下,不知你中槍了沒有嫁赏。
1.濫用表達(dá)式作為函數(shù)參數(shù)默認(rèn)值
Python允許開發(fā)者指定一個默認(rèn)值給函數(shù)參數(shù)其掂,雖然這是該語言的一個特征,但當(dāng)參數(shù)可變時(shí)潦蝇,很容易導(dǎo)致混亂款熬,例如,下面這段函數(shù)定義:
>>> def foo(bar=[]): # bar is optional and defaults to [] if not specified
... bar.append("baz") # but this line could be problematic, as we'll see...
... return bar
在上面這段代碼里攘乒,一旦重復(fù)調(diào)用foo()函數(shù)(沒有指定一個bar參數(shù))贤牛,那么將一直返回'bar',因?yàn)闆]有指定參數(shù)则酝,那么foo()每次被調(diào)用的時(shí)候殉簸,都會賦予[]。下面來看看,這樣做的結(jié)果:
>>> foo()
["baz"]
>>> foo()
["baz", "baz"]
>>> foo()
["baz", "baz", "baz"]
解決方案:
>>> def foo(bar=None):
... if bar is None:# or if not bar:
... bar = []
... bar.append("baz")
... return bar
...
>>> foo()
["baz"]
>>> foo()
["baz"]
>>> foo()
["baz"]
2.錯誤地使用類變量
先看下面這個例子:
>>> class A(object):
... x = 1
...
>>> class B(A):
... pass
...
>>> class C(A):
... pass
...
>>> print A.x, B.x, C.x
1 1 1
這樣是有意義的:
>>> B.x = 2
>>> print A.x, B.x, C.x
1 2 1
再來一遍:
>>> A.x = 3
>>> print A.x, B.x, C.x
3 2 3
僅僅是改變了A.x喂链,為什么C.x也跟著改變了返十。
在Python中,類變量都是作為字典進(jìn)行內(nèi)部處理的椭微,并且遵循方法解析順序(MRO)洞坑。在上面這段代碼中,因?yàn)閷傩詘沒有在類C中發(fā)現(xiàn)蝇率,它會查找它的基類(在上面例子中只有A迟杂,盡管Python支持多繼承)。換句話說本慕,就是C自己沒有x屬性排拷,獨(dú)立于A,因此锅尘,引用 C.x其實(shí)就是引用A.x监氢。
3.為異常指定不正確的參數(shù)
假設(shè)代碼中有如下代碼:
>>> try:
... l = ["a", "b"]
... int(l[2])
... except ValueError, IndexError: # To catch both exceptions, right?
... pass
...
Traceback (most recent call last):
File "", line 3, in
IndexError: list index out of range
問題在這里,except語句并不需要這種方式來指定異常列表藤违。然而浪腐,在Python 2.x中,except Exception,e通常是用來綁定異常里的 第二參數(shù)顿乒,好讓其進(jìn)行更進(jìn)一步的檢查议街。因此,在上面這段代碼里璧榄,IndexError異常并沒有被except語句捕獲特漩,異常最后被綁定 到了一個名叫IndexError的參數(shù)上。
在一個異常語句里捕獲多個異常的正確方法是指定第一個參數(shù)作為一個元組骨杂,該元組包含所有被捕獲的異常涂身。與此同時(shí),使用as關(guān)鍵字來保證最大的可移植性腊脱,Python 2和Python 3都支持該語法访得。
>>> try:
... l = ["a", "b"]
... int(l[2])
... except (ValueError, IndexError) as e:
... pass
...
>>>
4.誤解Python規(guī)則范圍
Python的作用域解析是基于LEGB規(guī)則,分別是Local陕凹、Enclosing、Global鳄炉、Built-in杜耙。實(shí)際上,這種解析方法也有一些玄機(jī)拂盯,看下面這個例子:
>>> x = 10
>>> def foo():
... x += 1
... print x
...
>>> foo()
Traceback (most recent call last):
File "", line 1, in
File "", line 2, in foo
UnboundLocalError: local variable 'x' referenced before assignment
許多人會感動驚訝佑女,當(dāng)他們在工作的函數(shù)體里添加一個參數(shù)語句,會在先前工作的代碼里報(bào)UnboundLocalError錯誤( 點(diǎn)擊這里查看更詳細(xì)描述)。
在使用列表時(shí)团驱,開發(fā)者是很容易犯這種錯誤的摸吠,看看下面這個例子:
>>> lst = [1, 2, 3]
>>> def foo1():
lst.append(5) # This works ok
>>> foo1()
>>> lst
[1, 2, 3, 5]
>>> lst = [1, 2, 3]
>>> def foo2():
... lst += [5] # ... but this bombs!
...
>>> foo2()
Traceback (most recent call last):
File "", line 1, in
File "", line 2, in foo
UnboundLocalError: local variable 'lst' referenced before assignment
為什么foo2失敗而foo1運(yùn)行正常?
答案與前面那個例子是一樣的嚎花,但又有一些微妙之處寸痢。foo1沒有賦值給lst,而foo2賦值了紊选。lst += [5]實(shí)際上就是lst = lst + [5]啼止,試圖給lst賦值(因此,假設(shè)Python是在局部作用域里)兵罢。然而献烦,我們正在尋找指定給lst的值是基于lst本身,其實(shí)尚未確定卖词。
5.修改遍歷列表
下面這段代碼很明顯是錯誤的:
>>> odd = lambda x : bool(x % 2)
>>> numbers = [n for n in range(10)]
>>> for i in range(len(numbers)):
... if odd(numbers[i]):
... del numbers[i] # BAD: Deleting item from a list while iterating over it
...
Traceback (most recent call last):
File "", line 2, in
IndexError: list index out of range
在遍歷的時(shí)候巩那,對列表進(jìn)行刪除操作,這是很低級的錯誤此蜈。稍微有點(diǎn)經(jīng)驗(yàn)的人都不會犯即横。
對上面的代碼進(jìn)行修改,正確地執(zhí)行:
>>> odd = lambda x : bool(x % 2)
>>> numbers = [n for n in range(10)]
>>> numbers[:] = [n for n in numbers if not odd(n)] # ahh, the beauty of it all
>>> numbers
[0, 2, 4, 6, 8]
6.如何在閉包中綁定變量
看下面這個例子:
>>> def create_multipliers():
... return [lambda x : i * x for i in range(5)]
>>> for multiplier in create_multipliers():
... print multiplier(2)
...
你期望的結(jié)果是:
0
2
4
6
8
實(shí)際上:
8
8
8
8
8
是不是非常吃驚舶替!出現(xiàn)這種情況主要是因?yàn)镻ython的后期綁定行為令境,該變量在閉包中使用的同時(shí),內(nèi)部函數(shù)又在調(diào)用它顾瞪。
解決方案:
>>> def create_multipliers():
... return [lambda x, i=i : i * x for i in range(5)]
...
>>> for multiplier in create_multipliers():
... print multiplier(2)
...
0
2
4
6
8
7.創(chuàng)建循環(huán)模塊依賴關(guān)系
假設(shè)有兩個文件舔庶,a.py和b.py,然后各自導(dǎo)入陈醒,如下:
在a.py中:
import b
def f():
return b.x
print f()
在b.py中:
import a
x = 1
def g():
print a.f()
首先惕橙,讓我們試著導(dǎo)入a.py:
>>> import a
1
可以很好地工作,也許你會感到驚訝钉跷。畢竟弥鹦,我們確實(shí)在這里做了一個循環(huán)導(dǎo)入,難道不應(yīng)該有點(diǎn)問題嗎爷辙?
僅僅存在一個循環(huán)導(dǎo)入并不是Python本身問題彬坏,如果一個模塊被導(dǎo)入,Python就不會試圖重新導(dǎo)入膝晾。根據(jù)這一點(diǎn)栓始,每個模塊在試圖訪問函數(shù)或變量時(shí),可能會在運(yùn)行時(shí)遇到些問題血当。
當(dāng)我們試圖導(dǎo)入b.py會發(fā)生什么(先前沒有導(dǎo)入a.py):
>>> import b
Traceback (most recent call last):
File "", line 1, in
File "b.py", line 1, in
import a
File "a.py", line 6, in
print f()
File "a.py", line 4, in f
return b.x
AttributeError: 'module' object has no attribute 'x'
出錯了幻赚,這里的問題是禀忆,在導(dǎo)入b.py的過程中還要試圖導(dǎo)入a.py,這樣就要調(diào)用f()落恼,并且試圖訪問b.x箩退。但是b.x并未被定義。
可以這樣解決佳谦,僅僅修改b.py導(dǎo)入到a.py中的g()函數(shù):
x = 1
def g():
import a# This will be evaluated only when g() is called
print a.f()
無論何時(shí)導(dǎo)入戴涝,一切都可以正常運(yùn)行:
>>> import b
>>> b.g()
1# Printed a first time since module 'a' calls 'print f()' at the end
1# Printed a second time, this one is our call to 'g'
8.與Python標(biāo)準(zhǔn)庫模塊名稱沖突
Python擁有非常豐富的模塊庫,并且支持“開箱即用”吠昭。因此喊括,如果不刻意避免,很容易發(fā)生命名沖突事件矢棚。例如郑什,在你的代碼中可能有一個email.py的模塊,由于名稱一致蒲肋,它很有可能與Python自帶的標(biāo)準(zhǔn)庫模塊發(fā)生沖突蘑拯。
9.未按規(guī)定處理Python2.x和Python3.x之間的區(qū)別
看一下foo.py:
import sys
def bar(i):
if i == 1:
raise KeyError(1)
if i == 2:
raise ValueError(2)
def bad():
e = None
try:
bar(int(sys.argv[1]))
except KeyError as e:
print('key error')
except ValueError as e:
print('value error')
print(e)
bad()
在Python 2里面可以很好地運(yùn)行:
$ python foo.py 1
key error
1
$ python foo.py 2
value error
2
但是在Python 3里:
$ python3 foo.py 1
key error
Traceback (most recent call last):
File "foo.py", line 19, in
bad()
File "foo.py", line 17, in bad
print(e)
UnboundLocalError: local variable 'e' referenced before assignment
解決方案:
import sys
def bar(i):
if i == 1:
raise KeyError(1)
if i == 2:
raise ValueError(2)
def good():
exception = None
try:
bar(int(sys.argv[1]))
except KeyError as e:
exception = e
print('key error')
except ValueError as e:
exception = e
print('value error')
print(exception)
good()
在Py3k中運(yùn)行結(jié)果:
$ python3 foo.py 1
key error
1
$ python3 foo.py 2
value error
2
在 Python招聘指南里有許多關(guān)于Python 2與Python 3在移植代碼時(shí)需要關(guān)注的注意事項(xiàng)與討論,大家可以前往看看兜粘。
10.濫用__del__方法
比如這里有一個叫mod.py的文件:
import foo
class Bar(object):
...
def __del__(self):
foo.cleanup(self.myhandle)
下面申窘,你在another_mod.py文件里執(zhí)行如下操作:
import mod
mybar = mod.Bar()
你會獲得一個AttributeError異常。
至于為什么會出現(xiàn)該異常孔轴,點(diǎn)擊這里查看詳情剃法。當(dāng)解釋器關(guān)閉時(shí),該模塊的全局變量全部設(shè)置為None路鹰。因此贷洲,在上面這個例子里,當(dāng)__del__被調(diào)用時(shí)晋柱,foo已經(jīng)全部被設(shè)置為None优构。
一個很好的解決辦法是使用atexit.register()代替。順便說一句雁竞,當(dāng)程序執(zhí)行完成后钦椭,您注冊的處理程序會在解釋器關(guān)閉之前停止 工作。
修復(fù)上面問題的代碼:
import foo
import atexit
def cleanup(handle):
foo.cleanup(handle)
class Bar(object):
def __init__(self):
...
atexit.register(cleanup, self.myhandle)
在程序的正常終止的前提下碑诉,這個實(shí)現(xiàn)提供了一個整潔可靠的方式調(diào)用任何需要清理的功能彪腔。
總結(jié)
Python是一款強(qiáng)大而靈活的編程語言,并且?guī)в性S多機(jī)制和模式來大大提高工作效率进栽。正如任何一門語言或軟件工具一樣漫仆,人們對其能力都會存在一個限制性地理解或欣賞,有些是弊大于利泪幌,有些時(shí)候反而會帶來一些陷進(jìn)盲厌。 體會一名語言的細(xì)微之處,理解一些常見的陷阱祸泪,有助于你在開發(fā)者的道路上走的更遠(yuǎn)吗浩。