python開發者常犯的10個錯誤(轉)
常見錯誤1:錯誤地將表示式作為函式的預設引數
在Python中,我們可以為函式的某個引數設定預設值,使該引數成為可選引數。雖然這是一個很好的語言特性,但是當預設值是可變型別時,也會導致一些令人困惑的情況。我們來看看下面這個Python函式定義:
>>> def foo(bar=[]): # bar是可選引數,如果沒有提供bar的值,則預設為[], ... bar.append("baz") # 但是稍後我們會看到這行程式碼會出現問題。 ... return bar
Python程式設計師常犯的一個錯誤,就是想當然地認為:在每次呼叫函式時,如果沒有為可選引數傳入值,那麼這個可選引數就會被設定為指定的預設值。在上面的程式碼中,你們可能覺得重複呼叫foo()函式應該會一直返回'baz',因為你們預設每次foo()
bar
變數的值),bar
變數都被設定為[](也就是,一個新的空列表)。
但是,實際執行結果卻是這樣的:
>>> foo() ["baz"] >>> foo() ["baz", "baz"] >>> foo() ["baz", "baz", "baz"]
很奇怪吧?為什麼每次呼叫foo()
函式時,都會把"baz"這個預設值新增到已有的列表中,而不是重新建立一個新的空列表呢?
答案就是,可選引數預設值的設定在Python中只會被執行一次,也就是定義該函式的時候。因此,只有當foo()
函式被定義時,bar
foo()
函式被呼叫時,都會繼續使用bar
引數原先初始化生成的那個列表。
當然,一個常見的解決辦法就是:
>>> 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
在Python語言中,類變數是以字典的形式進行處理的,並且遵循方法解析順序(Method Resolution Order,MRO)。因此,在上面的程式碼中,由於類C中並沒有x
這個屬性,直譯器將會查詢它的基類(base class,儘管Python支援多重繼承,但是在這個例子中,C的基類只有A)。換句話說,C並不沒有獨立於A、真正屬於自己的x
屬性。所以,引用C.x
實際上就是引用了A.x
。如果沒有處理好這裡的關係,就會導致示例中出現的這個問題。
常見錯誤3:錯誤地指定異常程式碼塊(exception block)的引數
請看下面這段程式碼:
>>> try: ... l = ["a", "b"] ... int(l[2]) ... except ValueError, IndexError: # To catch both exceptions, right? ... pass ... Traceback (most recent call last): File "<stdin>", line 3, in <module> IndexError: list index out of range
這段程式碼的問題在於,except
語句並不支援以這種方式指定異常。在Python 2.x中,需要使用變數e
將異常繫結至可選的第二個引數中,才能進一步檢視異常的情況。因此,在上述程式碼中,except
語句並沒有捕獲IndexError異常;而是將出現的異常繫結到了一個名為IndexError
的引數中。
要想在except
語句中正確地捕獲多個異常,則應將第一個引數指定為元組,然後在元組中寫下希望捕獲的異常型別。另外,為了提高可移植性,請使用as
關鍵詞,Python 2和Python 3均支援這種用法。
>>> try: ... l = ["a", "b"] ... int(l[2]) ... except (ValueError, IndexError) as e: ... pass ... >>>
常見錯誤4:錯誤理解Python中的變數名解析
Python中的變數名解析遵循所謂的LEGB
原則,也就是“L:本地作用域;E:上一層結構中def或lambda的本地作用域;G:全域性作用域;B:內建作用域”(Local,Enclosing,Global,Builtin),按順序查詢。看上去是不是很簡單?不過,事實上這個原則的生效方式還是有著一些特殊之處。說到這點,我們就不得不提下面這個常見的Python程式設計錯誤。請看下面的程式碼:
>>> x = 10 >>> def foo(): ... x += 1 ... print x ... >>> foo() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in foo UnboundLocalError: local variable 'x' referenced before assignment
出了什麼問題?
上述錯誤的出現,是因為當你在某個作用域內為變數賦值時,該變數被Python直譯器自動視作該作用域的本地變數,並會取代任何上一層作用域中相同名稱的變數。
正是因為這樣,才會出現一開始好好的程式碼,在某個函式內部添加了一個賦值語句之後卻出現了UnboundLocalError
,難怪會讓許多人吃驚。
在使用列表時,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() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in foo UnboundLocalError: local variable 'lst' referenced before assignment
呃?為什麼函式foo1
執行正常,foo2
卻出現了錯誤?
答案與上一個示例相同,但是卻更難捉摸清楚。foo1
函式並沒有為lst
變數進行賦值,但是foo2
卻有賦值。我們知道,lst += [5]
只是lst = lst + [5]
的簡寫,從中我們就可以看出,foo2
函式在嘗試為lst
賦值(因此,被Python直譯器認為是函式本地作用域的變數)。但是,我們希望為lst
賦的值卻又是基於lst
變數本身(這時,也被認為是函式本地作用域內的變數),也就是說該變數還沒有被定義。這才出現了錯誤。
常見錯誤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 "<stdin>", line 2, in <module> IndexError: list index out of range
在遍歷列表或陣列的同時從中刪除元素,是任何經驗豐富的Python開發人員都會注意的問題。但是儘管上面的示例十分明顯,資深開發人員在編寫更為複雜程式碼的時候,也很可能會無意之下犯同樣的錯誤。
幸運的是,Python語言融合了許多優雅的程式設計正規化,如果使用得當,可以極大地簡化程式碼。簡化程式碼還有一個好處,就是不容易出現在遍歷列表時刪除元素這個錯誤。能夠做到這點的一個程式設計正規化就是列表解析式。而且,列表解析式在避免這個問題方面尤其有用,下面用列表解析式重新實現上面程式碼的功能:
>>> 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:不理解Python在閉包中如何繫結變數
請看下面這段程式碼:
>>> def create_multipliers(): ... return [lambda x : i * x for i in range(5)] >>> for multiplier in create_multipliers(): ... print multiplier(2) ...
你可能覺得輸出結果應該是這樣的:
0 2 4 6 8
但是,實際的輸出結果卻是:
8 8 8 8 8
嚇了一跳吧!
這個結果的出現,主要是因為Python中的遲繫結(late binding )機制,即閉包中變數的值只有在內部函式被呼叫時才會進行查詢。因此,在上面的程式碼中,每次create_multipliers()
所返回的函式被呼叫時,都會在附近的作用域中查詢變數i的值(而到那時,迴圈已經結束,所以變數i最後被賦予的值為4)。
要解決這個常見Python問題的方法中,需要使用一些hack技巧:
>>> 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
請注意!我們在這裡利用了預設引數來實現這個lambda匿名函式。有人可能認為這樣做很優雅,有人會覺得很巧妙,還有人會嗤之以鼻。但是,如果你是一名Python程式設計師,不管怎樣你都應該要了解這種解決方法。
常見錯誤7:模組之間出現迴圈依賴(circular dependencies)
假設你有兩個檔案,分別是a.py
和b.py
,二者相互引用,如下所示:
a.py
檔案中的程式碼:
import b def f(): return b.x print f()
b.py
檔案中的程式碼:
import a x = 1 def g(): print a.f()
首先,我們嘗試匯入a.py
模組:
>>> import a 1
程式碼執行正常。也許這出乎了你的意料。畢竟,我們這裡存在迴圈引用這個問題,想必應該是會出現問題的,難道不是嗎?
答案是,僅僅存在迴圈引用的情況本身並不會導致問題。如果一個模組已經被引用了,Python可以做到不再次進行引用。但是如果每個模組試圖訪問其他模組定義的函式或變數的時機不對,那麼你就很可能陷入困境。
那麼回到我們的示例,當我們匯入a.py
模組時,它在引用b.py
模組時是不會出現問題的,因為b.py
模組在被引用時,並不需要訪問在a.py
模組中定義的任何變數或函式。b.py
模組中對a模組唯一的引用,就是呼叫了a模組的foo()
函式。但是那個函式呼叫發生在g()
函式當中,而a.py
或b.py
模組中都沒有呼叫g()
函式。所以,不會出現問題。
但是,如果我們試著匯入b.py
模組呢(即之前沒有引用a.py
模組的前提下):
>>> import b Traceback (most recent call last): File "<stdin>", line 1, in <module> File "b.py", line 1, in <module> import a File "a.py", line 6, in <module> print f() File "a.py", line 4, in f return b.x AttributeError: 'module' object has no attribute 'x'
糟糕。情況不太妙!這裡的問題是,在匯入b.py
的過程中,它試圖引用a.py
模組,而a.py
模組接著又要呼叫foo()
函式,這個foo()
函式接著又試圖去訪問b.x
變數。但是這個時候,b.x
變數還沒有被定義,所以才出現了AttributeError異常。
解決這個問題有一種非常簡單的方法,就是簡單地修改下b.py
模組,在g()
函式內部才引用a.py
:
x = 1 def g(): import a # This will be evaluated only when g() is called print a.f()
現在我們再匯入b.py
模組的話,就不會出現任何問題了:
>>> 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標準庫模組名衝突
Python語言的一大優勢,就是其本身自帶的強大標準庫。但是,正因為如此,如果你不去刻意注意的話,你也是有可能為自己的模組取一個和Python自帶標準庫模組相同的名字(例如,如果你的程式碼中有一個模組叫email.py
,那麼這就會與Python標準庫中同名的模組相沖突。)
這很可能會給你帶來難纏的問題。舉個例子,在匯入模組A的時候,假如該模組A試圖引用Python標準庫中的模組B,但卻因為你已經有了一個同名模組B,模組A會錯誤地引用你自己程式碼中的模組B,而不是Python標準庫中的模組B。這也是導致一些嚴重錯誤的原因。
因此,Python程式設計師要格外注意,避免使用與Python標準庫模組相同的名稱。畢竟,修改自己模組的名稱比提出PEP提議修改上游模組名稱且讓提議通過,要來得容易的多。
常見錯誤9:未能解決Python 2與Python 3之間的差異
假設有下面這段程式碼:
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,那麼程式碼執行正常:
$ 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 <module> bad() File "foo.py", line 17, in bad print(e) UnboundLocalError: local variable 'e' referenced before assignment
這到底是怎麼回事?這裡的“問題”是,在Python 3中,異常物件在except
程式碼塊作用域之外是無法訪問的。(這麼設計的原因在於,如果不這樣的話,堆疊幀中就會一直保留它的引用迴圈,直到垃圾回收器執行,將引用從記憶體中清除。)
避免這個問題的一種方法,就是在except
程式碼塊的作用域之外,維持一個對異常物件的引用(reference),這樣異常物件就可以訪問了。下面這段程式碼就使用了這種方法,因此在Python 2和Python 3中的輸出結果是一致的:
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()
在Python 3下執行程式碼:
$ python3 foo.py 1 key error 1 $ python3 foo.py 2 value error 2
太棒了!
常見錯誤10:錯誤使用del方法
假設你在mod.py
的檔案中編寫了下面的程式碼:
import foo class Bar(object): ... def __del__(self): foo.cleanup(self.myhandle)
之後,你在another_mod.py
檔案中進行如下操作:
import mod mybar = mod.Bar()
如果你執行another_mod.py
模組的話,將會出現AttributeError異常。
為什麼?因為當直譯器結束執行的時候,該模組的全域性變數都會被設定為None
。因此,在上述示例中,當__del__
方法被呼叫之前,foo
已經被設定成了None
。
要想解決這個有點棘手的Python程式設計問題,其中一個辦法就是使用atexit.register()
方法。這樣的話,當你的程式執行完成之後(即正常退出程式的情況下),你所指定的處理程式就會在直譯器關閉之前執行。
應用了上面這種方法,修改後的mod.py
檔案可能會是這樣子的:
import foo import atexit def cleanup(handle): foo.cleanup(handle) class Bar(object): def __init__(self): ... atexit.register(cleanup, self.myhandle)
這種實現支援在程式正常終止時乾淨利落地呼叫任何必要的清理功能。很明顯,上述示例中將會由foo.cleanup
函式來決定如何處理self.myhandle
所繫結的物件。
綜述
Python是一門強大而又靈活的程式語言,提供的許多程式設計機制和正規化可以極大地提高工作效率。但是與任何軟體工具或語言一樣,如果對該語言的能力理解有限或無法欣賞,那麼有時候自己反而會被阻礙,而不是受益了。正如一句諺語所說,“自以為知道夠多,但實則會給自己或別人帶來危險”(knowing enough to be dangerous)。(譯者注:這句諺語的意思是,自以為已經對某件事情瞭解足夠,但在實際去執行或實施時,卻會給自己和別人帶來危險。)
不斷地熟悉Python語言的一些細微之處,尤其是本文中提到的10大常見錯誤,將會幫助你有效地使用這門語言,同時也能避免犯一些比較常見的錯誤。
轉自https://blog.csdn.net/heybob/article/details/52250792
英文版https://www.toptal.com/python/top-10-mistakes-that-python-programmers-make
相關推薦
python開發者常犯的10個錯誤(轉)
常見錯誤1:錯誤地將表示式作為函式的預設引數 在Python中,我們可以為函式的某個引數設定預設值,使該引數成為可選引數。雖然這是一個很好的語言特性,但是當預設值是可變型別時,也會導致一些令人困惑的情況。我們來看看下面這個Python函式定義: >>> def foo(bar=[])
Python初學者常犯的簡單錯誤集合與練習
在本專案中,有多段Python程式碼,初學者可通過修復這些程式碼的錯誤來加強你的Python學習, 專案地址:https://github.com/qxf2/wtfiswronghere(點選閱讀原文即可訪問) 我們提供了包含錯誤的小段程式碼示例。最初這些程式碼段只有一個錯誤,並且相對容易發
Python小白的10個常犯錯誤,你還在這樣操作嗎?
下面是十個Python中很有用的貼士和技巧。其中一些是初學這門語言常常會犯的錯誤。 注意:假設我們都用的是Python 3 1.
Java程序員們最常犯的10個錯誤
table 程序 編譯期 con 引用 必須 排序 left 很多 Java程序員們最常犯的10個錯誤 1.將數組轉化為一個列表時,程序員們經常這樣做: List<String> list = Arrays.asList(arr); Arrays.a
新手初學Python最常犯的十個錯誤,其中有你麼
Python學習交流群865597862!進群免費領取2018最全學習資料! 1. 忘記寫冒號 在 if、elif、else、for、while、class、def 語句後面忘記新增“:” if spam == 42 print('Hello!') 2. 誤用
新Windows管理員常犯的10個錯誤
Whether you’re new to network administration or to the Windows environment, a few common oversights and mistakes can trip you up. Deb Shinder explains how
Java 開發者最容易犯的10個錯誤
我最近在學習 Java,覺得這篇舊文不錯,就翻譯了一下,感覺對新手有些幫助。 原文:www.programcreek.com/2014/05/top… 顧問:張博(Gradle 開發者之一) 譯文開始 這 10 個錯誤是我綜合 GitHub 上的專案、StackOverflow 上的問答和 Goo
AngularJS開發人員最常犯的10個錯誤
簡介 AngularJS是目前最為活躍的Javascript框架之一,AngularJS的目標之一是簡化開發過程,這使得AngularJS非常善於構建小型app原型,但AngularJS對於全功能的客戶端應用程式同樣強大,它結合了開發簡便,特性廣泛和出眾的效能,使其
Python入門常犯的錯誤有哪些?
Python以其簡單易懂的語法格式與其它語言形成鮮明對比,初學者遇到最多的問題就是不按照 Python 的規則來寫,即便是有程式設計經驗的程式設計師,也容易按照固有的思維和語法格式來寫 Python 程式碼,這樣就容易發生錯誤,以下列舉了Python入門容易犯的錯誤! 1.
Java程式設計師容易犯的10個錯誤
Array 轉 ArrayList 一般開發者喜歡用: List list = Arrays.asList(arr); Arrays.asList() 會返回一個ArrayList,這是Arrays裡內嵌的一個私有靜態類,而並不是java.util.Arra
Java 程式設計師在寫 SQL 時常犯的 10 個錯誤
Java程式設計師程式設計時需要混合面向物件思維和一般指令式程式設計的方法,能否完美的將兩者結合起來完全得依靠程式設計人員的水準:技能(任何人都能容易學會指令式程式設計)模式(有些人用“模式-模式”,舉個例子,模式可以應用到任何地方,而且都可以歸為某一類模式)心境(首先,要
版本 ------- 2017年最受開發者歡迎的10個Linux發行版
ogr cmak 但是 測試軟件 add 依賴項 xen 方法 roi 1.Arch Linux Arch Linux在安裝過程中提供了強大的可定制選擇,支持你下載和安裝自己所需的程序包。雖然這個選擇對新手來說沒有多大的幫助,但是它確實能夠幫助那些使用Arch構建系統和存
50k大牛告訴你Python怎麼學,10個特性帶你快速瞭解python
前言 如果你是一個正在學習python的c、c++ or java程式設計師,又或者你是剛剛接觸python,剛剛開始學習python,那麼,請認真看完這10個語言特性,你會受益匪淺的。 新增小編python學習群865597862即可領取2018最新全套python零基礎入門
獻給正在學習python的你,10個最受歡迎的Python開源框架
如果你正在學習python,那麼這10個開源框架,你可千萬別錯過,這些框架包括事件I/O,OLAP,Web開發,高效能網路通訊,測試,爬蟲等。雖說不上是全都有,但也足夠滿足你了。 Python 是一個高層次的結合瞭解釋性、編譯性、互動性和麵向物件的指令碼
使用Arduino開發板時最常見的10個錯誤
作為Arduino開發板的初學者,對於沒有電子背景的人來說非常具有挑戰性,你會遇到很多錯誤,其中一些可能有簡單的解決方案,但可能需要幾天的時間來解決。因此,為了使事情變得更容易,我製作了一個包含10個最常見的Arduino錯誤的列表,以及可能的解決方案。 Arduino
Python | 開發者必備的 6 個庫
reason 錯誤處理 change 下載 adt 選項 ongui ats 並不是 無論你是正在使用 Python 進行快速開發,還是在為 Python 桌面應用制作原生 UI ,或者是在優化現有的 Python 代碼,以下這些 Python 項目都是應該使用的。 Py
IT從業人員必看的10個論壇[轉自zOS/OS390中國技術社群]
IT從業人員必看的10個論壇IT方面的論壇太多了,有綜合,有專業,有行業,在各個論壇裡混了幾年,體會頗深,以前是論壇哪裡人多,往哪裡去,新浪論壇,網易是經常去的,人多啊,好幾十萬,去了以後才發現沒有意思,沒有共同的語言,於是逛專業論壇,行業論壇,終於找到了共同語言,總結一下論
適合區塊鏈開發者使用的10個工具
本文翻譯自DashMagazine《10 Tools for Blockchain Development》,原文連結:ht
小白學PYTHON時最容易犯的6個錯誤,看看你遇到過幾個
逗號 ice fault sep mpat 解釋器 github上 arw 別人 最近又在跟之前的同學一起學習python,一起進步,發現很多測試同學在初學python的時候很容易犯一些錯誤,特意總結了一下。其實這些錯誤不僅是在學python時會碰到,在學習其他語言的時候也
程序員最常犯的七個錯誤,開發年限越久的程序員反而越會犯這些錯
聽說 技術開發 都沒有 孤單 聯網 額外 borde 這樣的 tst 時間久了,我發現很多程序員總是犯著相同的錯誤,前仆後繼,卻毫不自知。 下面就是程序員在他們的軟件開發生涯中最常犯的7個錯誤。 ▼沒有明確的目標 心中沒有終點目標,那就只會隨波逐流。 如果你想在軟件開發