在學(xué)習(xí)python的過程中,相信大家都有踩過不少的坑,有些坑可能踩了不止一次,感覺就像是在坑與坑之間反復(fù)橫跳。那么如何避免這些坑呢?看完這篇文章,你就知道了。我們來談?wù)勎覀?b>學(xué)習(xí)python的過程中,最常見的七大坑:

1. 縮進(jìn),符號和空格不正確
寫代碼時(shí)大家會使用縮進(jìn)、對齊、空格等,這些是為了提高代碼的可讀性
在python語言中,縮進(jìn)是十分重要的
比如在創(chuàng)建一個(gè)新類時(shí),該類中的所有內(nèi)容都在聲明下縮進(jìn),決策、循環(huán)還有其它結(jié)構(gòu)語句也會出現(xiàn)類似的情況,
代碼執(zhí)行時(shí)如果存在問題,可以先查看一下縮進(jìn)是否正確。
這邊有個(gè)例子,在使用IF語句時(shí),請確保使用正確且合適的冒號和縮進(jìn),因?yàn)樗鼈儠?dǎo)致語法和縮進(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語句后面的:缺失;下一行沒有進(jìn)行正確的縮進(jìn),執(zhí)行代碼出錯(cuò)。
val = 500
if val > 100:
print(“value is grater then 100”)
value is grater then 100
更正這兩個(gè)問題之后,代碼可以很好的運(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,然后我們試著來改變一下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中,命名空間是名字到對象映射的結(jié)合,不同命名空間中的名字是沒有關(guān)聯(lián)的,這種映射的實(shí)現(xiàn)有點(diǎn)類似于python中的字典。
當(dāng)你名字訪問一個(gè)對象的屬性時(shí),先從對象的命名空間尋找。如果找到了這個(gè)屬性,就返回這個(gè)屬性的值;如果沒有找到的話,則從類的命名空間中尋找,找到了就返回這個(gè)屬性的值,找不到則拋出異常。
在Python中,類變量在內(nèi)部作為字典處理,并遵循通常稱為方法解析順序(MRO)的方法。
MRO:Method Resolution Order方法解析順序,Python支持多繼承,該方法用于解決父類存在同名函數(shù)的時(shí)存在的二義性問題。
因此在上面的代碼中,由于x在對象的命名空間中找不到該屬性C,因此將在類中查找它。換句話說,C沒有自己的x屬性,獨(dú)立于A。因此,引用C.x實(shí)際上是指A.x。
3.不了解python范圍規(guī)則
倘若不知道python的范圍規(guī)則,那么犯錯(cuò)誤的可能性會大大增加,這是因?yàn)镻ython使用一種獨(dú)有的范圍規(guī)則來確定變量范圍。
python范圍解析是基于LEGB規(guī)則,以下是Python范圍規(guī)則的概述:
·L -代表Local。它包含在函數(shù)內(nèi)指定的(標(biāo)識符/變量)名稱(使用def或lambda),而不是使用global關(guān)鍵字聲明。
·E -代表Enclosing function locals。它包含來自任何/所有封閉函數(shù)的本地范圍的名稱(例如,使用def或lambda)。
·G -指全球?qū)嶓w。它包括在模塊文件的頂層運(yùn)行或使用global關(guān)鍵字定義的名稱。
·B -指內(nèi)置插件。它跨越預(yù)先指定為內(nèi)置名稱的名稱,如打印,輸入,打開等。
LEGB規(guī)則指定名稱空間的以下順序,用于搜索名稱:
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ò)誤的原因是,對作用域中的變量進(jìn)行賦值時(shí),Python會自動將該變量視為該作用域的本地變量,并在外部作用域中隱藏任何類似命名的變量。
因此,許多人在代碼提示出錯(cuò)并顯示需要在函數(shù)中添加賦值語句而感到不解。
考慮一個(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]的簡寫,我們希望分配一個(gè)值給lst,但是分配的值lst是基于lst自身,但其尚未定義。
4. python閉包變量綁定
python的閉包變量問題也是新手們?nèi)菀谆煜囊粋€(gè)點(diǎn),來看看下面的例子:
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í)才會進(jìn)行查詢。
因此create_multipliers函數(shù)返回的lambda函數(shù)被調(diào)用時(shí),會在附近的作用域中查詢變量i的值,而在create_multipliers生成返回?cái)?shù)組之后,整數(shù)i的值是4,不會再改變,因此返回?cái)?shù)組中每個(gè)匿名函數(shù)實(shí)際上都是:lambda x: 4*x。、
解決辦法是將臨時(shí)值也保存在匿名函數(shù)的作用域內(nèi),在聲明匿名函數(shù)時(shí)就查詢變量的值。
了解原理之后,讓我們來改一改代碼,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. 名稱與Python標(biāo)準(zhǔn)庫模塊發(fā)生沖突
Python擁有大量的庫模塊,開箱即用。但是,如果您遇到一個(gè)模塊的名稱與Python附帶的標(biāo)準(zhǔn)庫中具有相同名稱的模塊之間的名稱沖突,則可能會出現(xiàn)問題。
例如導(dǎo)入另一個(gè)庫,而這個(gè)庫又會嘗試導(dǎo)入模塊的Python標(biāo)準(zhǔn)庫版本,但由于你有一個(gè)同名的模塊,另一個(gè)包會錯(cuò)誤地導(dǎo)入你的版本而不是Python標(biāo)準(zhǔn)庫。
因此,應(yīng)該注意避免使用與Python標(biāo)準(zhǔn)庫模塊中相同的名稱,并且更改包中的模塊名稱比提交Python Enhancement Proposal(PEP)以請求名稱更改更容易。
6.is和==/=和==
Python中有很多運(yùn)算符,例如is,=,==這三個(gè),許多剛剛?cè)腴T的新手會誤解這三個(gè)運(yùn)算符的意義和用法,以致于代碼出錯(cuò)。
在 Python 中會用到對象之間比較,可以用 ==,也可以用 is,但對對象比較判斷的內(nèi)容并不相同,區(qū)別在哪里?
·is 比較兩個(gè)對象的 id 值是否相等,是否指向同一個(gè)內(nèi)存地址,== 比較的是兩個(gè)對象的內(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é)果都為True,這是因?yàn)橹苯淤x值都是賦值的引用。如果新建對象之后,b 和 a 指向了不同的內(nèi)存,那么 b is a 的結(jié)果為False,而 b==a的結(jié)果為True。
·小整數(shù)對象[-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僅僅對比較小的整數(shù)對象進(jìn)行緩存(范圍為范圍[-5, 256])緩存起來,而并非是所有整數(shù)對象。需要注意的是,這僅僅是在命令行中執(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)存分配給新的類對象時(shí),它會自動被調(diào)用。
首先,init__并不相當(dāng)于C#中的構(gòu)造函數(shù),在執(zhí)行它的時(shí)候,實(shí)例已經(jīng)構(gòu)造出來。
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í)例化后的對象。
其次,子類可以不重寫__init,實(shí)例化子類時(shí),會自動調(diào)用超類中已定義的__init
。
class B(A):
def getName(self):
return B+self.name
if?name==main:
b=B(hello)
print (b.getName())
但如果重寫了__init__,實(shí)例化子類時(shí),則不會隱式的再去調(diào)用超類中已定義的__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í)行代碼則會報(bào)"AttributeError: ?C ?object has noattribute ?name ”錯(cuò)誤,所以如果重寫了__init__,為了能使用或擴(kuò)展超類中的行為,最好顯式的調(diào)用超類的__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)?覺得這篇文章對你有所幫助的話可以來個(gè)一鍵三連呀?。∪粲兄刚脑捒梢栽u論區(qū)見或者私我呀。