在學(xué)習(xí)python的過(guò)程中,相信大家都有踩過(guò)不少的坑棉钧,有些坑可能踩了不止一次屿脐,感覺(jué)就像是在坑與坑之間反復(fù)橫跳。那么如何避免這些坑呢宪卿?看完這篇文章的诵,你就知道了。我們來(lái)談?wù)勎覀?b>學(xué)習(xí)python的過(guò)程中佑钾,最常見(jiàn)的七大坑:
1. 縮進(jìn)奢驯,符號(hào)和空格不正確
寫(xiě)代碼時(shí)大家會(huì)使用縮進(jìn)、對(duì)齊次绘、空格等瘪阁,這些是為了提高代碼的可讀性
在python語(yǔ)言中撒遣,縮進(jìn)是十分重要的
比如在創(chuàng)建一個(gè)新類(lèi)時(shí),該類(lèi)中的所有內(nèi)容都在聲明下縮進(jìn)管跺,決策义黎、循環(huán)還有其它結(jié)構(gòu)語(yǔ)句也會(huì)出現(xiàn)類(lèi)似的情況,
代碼執(zhí)行時(shí)如果存在問(wèn)題豁跑,可以先查看一下縮進(jìn)是否正確廉涕。
這邊有個(gè)例子,在使用IF語(yǔ)句時(shí)艇拍,請(qǐng)確保使用正確且合適的冒號(hào)和縮進(jìn)狐蜕,因?yàn)樗鼈儠?huì)導(dǎo)致語(yǔ)法和縮進(jìn)錯(cuò)誤。
val = 500
if val > 100
print(“value is grater then 100”)
File “”, line 2
if val > 100
^
SyntaxError: invalid syntax
在上面的代碼當(dāng)中卸夕,出現(xiàn)了兩處錯(cuò)誤:if語(yǔ)句后面的:缺失层释;下一行沒(méi)有進(jìn)行正確的縮進(jìn),執(zhí)行代碼出錯(cuò)快集。
val = 500
if val > 100:
print(“value is grater then 100”)
value is grater then 100
更正這兩個(gè)問(wèn)題之后贡羔,代碼可以很好的運(yùn)行
2. 變量使用不正確
class A(object):x = 1
class B(A):pass
class C(A):pass
print( A.x, B.x, C.x)
1 1 1
這里輸出的值都是1,然后我們?cè)囍鴣?lái)改變一下A.x和B.x的值看看有什么變化个初。
B.x = 2
print (A.x, B.x, C.x)
A.x = 3
print (A.x, B.x, C.x)
1 2 1
3 2 3
我們只改變了A.x,為什么C.x改變呢乖寒?
這里需要了解到python的命名空間。
python中院溺,命名空間是名字到對(duì)象映射的結(jié)合楣嘁,不同命名空間中的名字是沒(méi)有關(guān)聯(lián)的,這種映射的實(shí)現(xiàn)有點(diǎn)類(lèi)似于python中的字典珍逸。
當(dāng)你名字訪問(wèn)一個(gè)對(duì)象的屬性時(shí)马澈,先從對(duì)象的命名空間尋找。如果找到了這個(gè)屬性弄息,就返回這個(gè)屬性的值痊班;如果沒(méi)有找到的話,則從類(lèi)的命名空間中尋找摹量,找到了就返回這個(gè)屬性的值涤伐,找不到則拋出異常。
在Python中缨称,類(lèi)變量在內(nèi)部作為字典處理凝果,并遵循通常稱(chēng)為方法解析順序(MRO)的方法。
MRO:Method Resolution Order方法解析順序睦尽,Python支持多繼承器净,該方法用于解決父類(lèi)存在同名函數(shù)的時(shí)存在的二義性問(wèn)題。
因此在上面的代碼中当凡,由于x在對(duì)象的命名空間中找不到該屬性C山害,因此將在類(lèi)中查找它纠俭。換句話說(shuō),C沒(méi)有自己的x屬性浪慌,獨(dú)立于A冤荆。因此,引用C.x實(shí)際上是指A.x权纤。
3.不了解python范圍規(guī)則
倘若不知道python的范圍規(guī)則钓简,那么犯錯(cuò)誤的可能性會(huì)大大增加,這是因?yàn)镻ython使用一種獨(dú)有的范圍規(guī)則來(lái)確定變量范圍汹想。
python范圍解析是基于LEGB規(guī)則外邓,以下是Python范圍規(guī)則的概述:
·L -代表Local。它包含在函數(shù)內(nèi)指定的(標(biāo)識(shí)符/變量)名稱(chēng)(使用def或lambda)古掏,而不是使用global關(guān)鍵字聲明损话。
·E -代表Enclosing function locals。它包含來(lái)自任何/所有封閉函數(shù)的本地范圍的名稱(chēng)(例如冗茸,使用def或lambda)。
·G -指全球?qū)嶓w匹中。它包括在模塊文件的頂層運(yùn)行或使用global關(guān)鍵字定義的名稱(chēng)夏漱。
·B -指內(nèi)置插件。它跨越預(yù)先指定為內(nèi)置名稱(chēng)的名稱(chēng)顶捷,如打印挂绰,輸入,打開(kāi)等服赎。
LEGB規(guī)則指定名稱(chēng)空間的以下順序葵蒂,用于搜索名稱(chēng):
Local - > Enclosed - > Global - > Built-in
考慮以下的例子:
x = 10
def foo():
x += 1
print(x)
foo()
UnboundLocalError Traceback (most recent call last):
in
in foo()
UnboundLocalError: local variable xreferenced before assignment
發(fā)生上述錯(cuò)誤的原因是,對(duì)作用域中的變量進(jìn)行賦值時(shí)重虑,Python會(huì)自動(dòng)將該變量視為該作用域的本地變量践付,并在外部作用域中隱藏任何類(lèi)似命名的變量。
因此缺厉,許多人在代碼提示出錯(cuò)并顯示需要在函數(shù)中添加賦值語(yǔ)句而感到不解永高。
考慮一個(gè)在使用列表時(shí)遇到的例子:
lst = [1, 2, 3]
def foo1():
lst.append(5)
foo1()
lst
[1, 2, 3, 5]
lst = [1, 2, 3]
def foo2():
lst += [5]
foo2()
UnboundLocalError ?Traceback (most recent call last):
in
in foo2()
UnboundLocalError: local variable lstreferenced before assignment
為什么foo2出錯(cuò)了但是foo1運(yùn)行良好?
答案在前面就已經(jīng)有所提示提针,在這個(gè)例子當(dāng)中foo1()做一個(gè)分配到lst命爬,而在foo2()當(dāng)中l(wèi)st += [5]其實(shí)只是lst = lst + [5]的簡(jiǎn)寫(xiě),我們希望分配一個(gè)值給lst辐脖,但是分配的值lst是基于lst自身饲宛,但其尚未定義。
4. python閉包變量綁定
python的閉包變量問(wèn)題也是新手們?nèi)菀谆煜囊粋€(gè)點(diǎn)嗜价,來(lái)看看下面的例子:
def create_multipliers():
return [lambda x : i * x for i in range(5)]
for multiplier in create_multipliers():
print (multiplier(2))
8
8
8
8
8
為什么結(jié)果是88888艇抠,和我所想的02468不一樣呢幕庐?
這是由于Python的遲綁定(late binding)機(jī)制,閉包中內(nèi)部函數(shù)的值只有在被調(diào)用時(shí)才會(huì)進(jìn)行查詢练链。
因此create_multipliers函數(shù)返回的lambda函數(shù)被調(diào)用時(shí)翔脱,會(huì)在附近的作用域中查詢變量i的值,而在create_multipliers生成返回?cái)?shù)組之后媒鼓,整數(shù)i的值是4届吁,不會(huì)再改變,因此返回?cái)?shù)組中每個(gè)匿名函數(shù)實(shí)際上都是:lambda x: 4*x绿鸣。疚沐、
解決辦法是將臨時(shí)值也保存在匿名函數(shù)的作用域內(nèi),在聲明匿名函數(shù)時(shí)就查詢變量的值潮模。
了解原理之后亮蛔,讓我們來(lái)改一改代碼,surprise擎厢!
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
5. 名稱(chēng)與Python標(biāo)準(zhǔn)庫(kù)模塊發(fā)生沖突
Python擁有大量的庫(kù)模塊究流,開(kāi)箱即用。但是动遭,如果您遇到一個(gè)模塊的名稱(chēng)與Python附帶的標(biāo)準(zhǔn)庫(kù)中具有相同名稱(chēng)的模塊之間的名稱(chēng)沖突芬探,則可能會(huì)出現(xiàn)問(wèn)題。
例如導(dǎo)入另一個(gè)庫(kù)厘惦,而這個(gè)庫(kù)又會(huì)嘗試導(dǎo)入模塊的Python標(biāo)準(zhǔn)庫(kù)版本偷仿,但由于你有一個(gè)同名的模塊,另一個(gè)包會(huì)錯(cuò)誤地導(dǎo)入你的版本而不是Python標(biāo)準(zhǔn)庫(kù)宵蕉。
因此酝静,應(yīng)該注意避免使用與Python標(biāo)準(zhǔn)庫(kù)模塊中相同的名稱(chēng),并且更改包中的模塊名稱(chēng)比提交Python Enhancement Proposal(PEP)以請(qǐng)求名稱(chēng)更改更容易羡玛。
6.is和==/=和==
Python中有很多運(yùn)算符别智,例如is,=稼稿,==這三個(gè)亿遂,許多剛剛?cè)腴T(mén)的新手會(huì)誤解這三個(gè)運(yùn)算符的意義和用法,以致于代碼出錯(cuò)渺杉。
在 Python 中會(huì)用到對(duì)象之間比較蛇数,可以用 ==,也可以用 is是越,但對(duì)對(duì)象比較判斷的內(nèi)容并不相同耳舅,區(qū)別在哪里?
·is 比較兩個(gè)對(duì)象的 id 值是否相等,是否指向同一個(gè)內(nèi)存地址浦徊,== 比較的是兩個(gè)對(duì)象的內(nèi)容是否相等馏予,值是否相等;
a = [“Python”]
b = a
b is a
True
id(a)
2222222
id(b)
2222222
b == a
True
可以發(fā)現(xiàn)上面的例子當(dāng)中b和a的內(nèi)存地址是相同的盔性,它們指向同一塊內(nèi)存霞丧,因而 is 和 == 的結(jié)果都為T(mén)rue,這是因?yàn)橹苯淤x值都是賦值的引用。如果新建對(duì)象之后冕香,b 和 a 指向了不同的內(nèi)存蛹尝,那么 b is a 的結(jié)果為False,而 b==a的結(jié)果為T(mén)rue悉尾。
·小整數(shù)對(duì)象[-5,256]在全局解釋器范圍內(nèi)被放入緩存供重復(fù)使用突那,例如:
a = 1
b = 1
a is b
True
a == b
True
a = 257
b = 257
a is b
False
Python僅僅對(duì)比較小的整數(shù)對(duì)象進(jìn)行緩存(范圍為范圍[-5, 256])緩存起來(lái),而并非是所有整數(shù)對(duì)象构眯。需要注意的是愕难,這僅僅是在命令行中執(zhí)行,而在Pycharm或者保存為文件執(zhí)行惫霸,結(jié)果是不一樣的猫缭,這是因?yàn)榻忉屍髯隽艘徊糠謨?yōu)化。
=和==的含義不同:
=代表的含義是賦值壹店,將某一數(shù)值賦給某個(gè)變量猜丹,比如a=3,將3這個(gè)數(shù)值賦予給a茫打。
==是判斷是否相等居触,返回True或False妖混,比如1==1老赤。他們是相等的,那么就返回true制市。1==2抬旺,他們是不相等的,那么就返回false祥楣。
例子:
a = [1,2]
b = [1,2]
c = a
a is b
False
a is c
true
a == b
true
7. 濫用__init__
init__方法在Python中用作構(gòu)造函數(shù)开财,當(dāng)Python將內(nèi)存分配給新的類(lèi)對(duì)象時(shí),它會(huì)自動(dòng)被調(diào)用误褪。
首先责鳍,init__并不相當(dāng)于C#中的構(gòu)造函數(shù),在執(zhí)行它的時(shí)候兽间,實(shí)例已經(jīng)構(gòu)造出來(lái)历葛。
class A(object):
def?init(self,name):
self.name=name
def getName(self):
return A+self.name
執(zhí)行代碼:
a=A(hello)
可以理解為:
a=object.new(A)
A.init(a,hello)
即__init__作用是初始化已實(shí)例化后的對(duì)象。
其次嘀略,子類(lèi)可以不重寫(xiě)__init恤溶,實(shí)例化子類(lèi)時(shí)乓诽,會(huì)自動(dòng)調(diào)用超類(lèi)中已定義的__init
。
class B(A):
def getName(self):
return B+self.name
if?name==main:
b=B(hello)
print (b.getName())
但如果重寫(xiě)了__init__咒程,實(shí)例化子類(lèi)時(shí)鸠天,則不會(huì)隱式的再去調(diào)用超類(lèi)中已定義的__init__。
class C(A):
def?init(self):
pass
def getName(self):
return C+self.name
if?name==main:
c=C()
print (c.getName())
此時(shí)執(zhí)行代碼則會(huì)報(bào)"AttributeError: ?C ?object has noattribute ?name ”錯(cuò)誤帐姻,所以如果重寫(xiě)了__init__稠集,為了能使用或擴(kuò)展超類(lèi)中的行為,最好顯式的調(diào)用超類(lèi)的__init__方法卖宠。
class C(A):
def?init(self,name):
super(C,self).init(name)
def getName(self):
return C+self.name
if?name==main:
c=C(hello)
print (c.getName())
老天爺都給我點(diǎn)贊了巍杈,你還不點(diǎn)?覺(jué)得這篇文章對(duì)你有所幫助的話可以來(lái)個(gè)一鍵三連呀?肝椤筷畦!若有指正的話可以評(píng)論區(qū)見(jiàn)或者私我呀。