送給小白的 7 個 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
/<ipython-input-1-a271e37c300f>


在上面的代碼當中,出現了兩處錯誤:if語句後面的:缺失;下一行沒有進行正確的縮進,執行代碼出錯。

val = 500
if val > 100:
print("value is grater then 100")
value is grater then 100


當你更正上述代碼中的兩個問題後,你會發現整段代碼能夠很好的運行。

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中,命名空間是名字到對象映射的結合,不同命名空間中的名字是沒有關聯的,這種映射的實現有點類似於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()
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
/<ipython-input-26-234e54482865>/<module>/<ipython-input-26-234e54482865>


發生上述錯誤的原因是,對作用域中的變量進行賦值時,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
/<ipython-input-30-579469eed71a>/<module>/<ipython-input-30-579469eed71a>


為什麼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#中的構造函數,在執行它的時候,實例已經構造出來。

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())


分享到:


相關文章: