Python語言簡單易用,但容易給新入門的朋友造成一些微妙的,難以捕捉的錯誤,稍不注意就入坑了。
是以,今天給大家總結一些易犯的小錯誤,讓你輕松進行不踩坑的Python學習。
1、縮進,符号和空格不正确
寫代碼時大家會使用縮進、對齊、空格等,其目的是為了提高代碼的可讀性。
但在python語言中,許多功能都依賴于縮進。
比如在建立一個新類時,該類中的所有内容都在聲明下縮進,決策、循環還有其它結構語句也會出現類似的情況,
如果你在代碼執行時發現問題,可以檢視一下是否使用了正确的縮進。
來看看下面的例子,在使用IF語句時,請確定使用正确且合适的冒号和縮進,因為它們會導緻文法和縮進錯誤。
val = 500
if val > 100
print("value is grater then 100")
File "<ipython-input-1-a271e37c300f>", line 2
if val > 100
^
SyntaxError: invalid syntax
在上面的代碼當中,出現了兩處錯誤:if語句後面的:缺失;下一行沒有進行正确的縮進,執行代碼出錯。
val = 500
if val > 100:
print("value is grater then 100")
value is grater then 100
當你更正上述代碼中的兩個問題後,你會發現整段代碼能夠很好的運作。
2、錯誤使用類變量
class A(object):x = 1class B(A):passclass C(A):passprint( 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中,命名空間是名字到對象映射的結合,不同命名空間中的名字是沒有關聯的,這種映射的實作有點類似于python中的字典。
當你名字通路一個對象的屬性時,先從對象的命名空間尋找。如果找到了這個屬性,就傳回這個屬性的值;如果沒有找到的話,則從類的命名空間中尋找,找到了就傳回這個屬性的值,找不到則抛出異常。
在Python中,類變量在内部作為字典處理,并遵循通常稱為方法解析順序(MRO)的方法。
MRO:Method Resolution Order 方法解析順序,Python支援多繼承,該方法用于解決父類存在同名函數的時存在的二義性問題。
是以在上面的代碼中,由于x在對象的命名空間中找不到該屬性C,是以将在類中查找它。換句話說,C沒有自己的x屬性,獨立于A。是以,引用C.x實際上是指A.x。
3、誤解python範圍規則
如果你不了解python的範圍規則,那麼你很容易犯錯誤,這是因為Python使用一種獨有的範圍規則來确定變量範圍。
python範圍解析是基于LEGB規則,以下是Python範圍規則的概述:
- ·L -代表Local。它包含在函數内指定的(辨別符/變量)名稱(使用def或lambda),而不是使用global關鍵字聲明。
- ·E -代表Enclosing function locals。它包含來自任何/所有封閉函數的本地範圍的名稱(例如,使用def或lambda)。
- ·G -指全球實體。它包括在子產品檔案的頂層運作或使用global關鍵字定義的名稱。
- ·B -指内置插件。它跨越預先指定為内置名稱的名稱,如列印,輸入,打開等。
LEGB規則指定名稱空間的以下順序,用于搜尋名稱:
Local - > Enclosed - > Global - > Built-in
考慮以下的例子:
x = 10
def foo():
x += 1
print(x)
foo()
#Python學習交流QQ群:857662006
UnboundLocalError Traceback (most recent call last):
<ipython-input-26-234e54482865> in <module>
<ipython-input-26-234e54482865> in foo()
UnboundLocalError: local variable 'x' referenced before assignment
發生上述錯誤的原因是,對作用域中的變量進行指派時,Python會自動将該變量視為該作用域的本地變量,并在外部作用域中隐藏任何類似命名的變量。
是以,許多人在代碼提示出錯并顯示需要在函數中添加指派語句而感到不解。
考慮一個在使用清單時遇到的例子:
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):
<ipython-input-30-579469eed71a> in <module>
<ipython-input-30-579469eed71a> in foo2()
UnboundLocalError: local variable 'lst' referenced before assignment
為什麼foo2出錯了但是foo1運作良好?
答案在前面就已經有所提示,在這個例子當中foo1()做一個配置設定到lst,而在foo2()當中lst += [5]其實隻是lst = lst + [5]的簡寫,我們希望配置設定一個值給lst,但是配置設定的值lst是基于lst自身,但其尚未定義。
4、python閉包變量綁定
python的閉包變量問題也是新手們容易混淆的一個點,來看看下面的例子:
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
為什麼結果是88888,和我所想的02468不一樣呢?
這是由于Python的遲綁定(late binding)機制,閉包中内部函數的值隻有在被調用時才會進行查詢。
是以create_multipliers函數傳回的lambda函數被調用時,會在附近的作用域中查詢變量i的值,而在create_multipliers生成傳回數組之後,整數i的值是4,不會再改變,是以傳回數組中每個匿名函數實際上都是:lambda x: 4*x。、
解決辦法是将臨時值也儲存在匿名函數的作用域内,在聲明匿名函數時就查詢變量的值。
了解原理之後,讓我們來改一改代碼,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标準庫子產品發生沖突
Python擁有大量的庫子產品,開箱即用。但是,如果您遇到一個子產品的名稱與Python附帶的标準庫中具有相同名稱的子產品之間的名稱沖突,則可能會出現問題。
例如導入另一個庫,而這個庫又會嘗試導入子產品的Python标準庫版本,但由于你有一個同名的子產品,另一個包會錯誤地導入你的版本而不是Python标準庫。
是以,應該注意避免使用與Python标準庫子產品中相同的名稱,并且更改包中的子產品名稱比送出Python Enhancement Proposal(PEP)以請求名稱更改更容易。
6、is和==/=和==
Python中有很多運算符,例如is,=,==這三個,許多剛剛入門的新手會誤解這三個運算符的意義和用法,以緻于代碼出錯。
在 Python 中會用到對象之間比較,可以用 ==,也可以用 is,但對對象比較判斷的内容并不相同,差別在哪裡?
·is 比較兩個對象的 id 值是否相等,是否指向同一個記憶體位址,== 比較的是兩個對象的内容是否相等,值是否相等;
a = ["Python"]
b = a
b is a
True
id(a)
2222222
id(b)
2222222
b == a
True
可以發現上面的例子當中b和a的記憶體位址是相同的,它們指向同一塊記憶體,因而 is 和 == 的結果都為True,這是因為直接指派都是指派的引用。如果建立對象之後,b 和 a 指向了不同的記憶體,那麼 b is a 的結果為False,而 b==a的結果為True。
·小整數對象[-5,256]在全局解釋器範圍内被放入緩存供重複使用,例如:
a = 1
b = 1
a is b
True
a == b
True
a = 257
b = 257
a is b
False
Python僅僅對比較小的整數對象進行緩存(範圍為範圍[-5, 256])緩存起來,而并非是所有整數對象。需要注意的是,這僅僅是在指令行中執行,而在Pycharm或者儲存為檔案執行,結果是不一樣的,這是因為解釋器做了一部分優化。
=和==的含義不同:
=代表的含義是指派,将某一數值賦給某個變量,比如a=3,将3這個數值賦予給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中用作構造函數,當Python将記憶體配置設定給新的類對象時,它會自動被調用。
首先,__init__并不相當于C#中的構造函數,在執行它的時候,執行個體已經構造出來。
#小編建立了一個Python學習交流QQ群:857662006
class A(object):
def __init__(self,name):
self.name=name
def getName(self):
return 'A '+self.name
執行代碼:
a=A('hello')
可以了解為:
a=object.__new__(A)
A.__init__(a,'hello')
即__init__作用是初始化已執行個體化後的對象。
其次,子類可以不重寫__init__,執行個體化子類時,會自動調用超類中已定義的__init__。
class B(A):
def getName(self):
return 'B '+self.name
if __name__=='__main__':
b=B('hello')
print (b.getName())
但如果重寫了__init__,執行個體化子類時,則不會隐式的再去調用超類中已定義的__init__。
class C(A):
def __init__(self):
pass
def getName(self):
return 'C '+self.name
if __name__=='__main__':
c=C()
print (c.getName())
此時執行代碼則會報"AttributeError: 'C' object has noattribute 'name'”錯誤,是以如果重寫了__init__,為了能使用或擴充超類中的行為,最好顯式的調用超類的__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())