Python開發者最常犯的10個錯誤
Python是一門簡單易學的程式語言,語法簡潔而清晰,並且擁有豐富和強大的類庫。與其它大多數程式設計語言使用大括號不一樣 ,它使用縮排來定義語句塊。
在平時的工作中,Python開發者很容易犯一些小錯誤,這些錯誤都很容易避免,本文總結了Python開發者最常犯的10個錯誤,一起來看下,不知你中槍了沒有。
1.濫用表示式作為函式引數預設值
Python允許開發者指定一個預設值給函式引數,雖然這是該語言的一個特徵,但當引數可變時,很容易導致混亂,例如,下面這段函式定義:
>>> def foo(bar=[]): # bar is optional and defaults to [] if not specified ... bar.append("baz") # but this line could be problematic, as we'll see... ... return bar
在上面這段程式碼裡,一旦重複呼叫foo()函式(沒有指定一個bar引數),那麼將一直返回'bar',因為沒有指定引數,那麼foo()每次被呼叫的時候,都會賦予[]。下面來看看,這樣做的結果:
>>> foo() ["baz"] >>> foo() ["baz", "baz"] >>> foo() ["baz", "baz", "baz"]
解決方案:
>>> 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
僅僅是改變了A.x,為什麼C.x也跟著改變了。
在Python中,類變數都是作為字典進行內部處理的,並且遵循方法解析順序(MRO)。在上面這段程式碼中,因為屬性x沒有在類C中發現,它會查詢它的基類(在上面例子中只有A,儘管Python支援多繼承)。換句話說,就是C自己沒有x屬性,獨立於A,因此,引用 C.x其實就是引用A.x。
3.為異常指定不正確的引數
假設程式碼中有如下程式碼:
>>> 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中,except Exception,e通常是用來繫結異常裡的 第二引數,好讓其進行更進一步的檢查。因此,在上面這段程式碼裡,IndexError異常並沒有被except語句捕獲,異常最後被繫結 到了一個名叫IndexError的引數上。
在一個異常語句裡捕獲多個異常的正確方法是指定第一個引數作為一個元組,該元組包含所有被捕獲的異常。與此同時,使用as關鍵字來保證最大的可移植性,Python 2和Python 3都支援該語法。
>>> try: ... l = ["a", "b"] ... int(l[2]) ... except (ValueError, IndexError) as e: ... pass ... >>>
4.誤解Python規則範圍
Python的作用域解析是基於LEGB規則,分別是Local、Enclosing、Global、Built-in。實際上,這種解析方法也有一些玄機,看下面這個例子:
>>> 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
許多人會感動驚訝,當他們在工作的函式體裡新增一個引數語句,會在先前工作的程式碼裡報UnboundLocalError錯誤( 點選這裡檢視更詳細描述)。
在使用列表時,開發者是很容易犯這種錯誤的,看看下面這個例子:
>>> lst = [1, 2, 3] >>> def foo1(): ... lst.append(5) # This works ok... ... >>> foo1() >>> lst [1, 2, 3, 5] >>> lst = [1, 2, 3] >>> def foo2(): ... lst += [5] # ... but this bombs! ... >>> 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
為什麼foo2失敗而foo1執行正常?
答案與前面那個例子是一樣的,但又有一些微妙之處。foo1沒有賦值給lst,而foo2賦值了。lst += [5]實際上就是lst = lst + [5],試圖給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
在遍歷的時候,對列表進行刪除操作,這是很低階的錯誤。稍微有點經驗的人都不會犯。
對上面的程式碼進行修改,正確地執行:
>>> 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.如何在閉包中繫結變數
看下面這個例子:
>>> def create_multipliers(): ... return [lambda x : i * x for i in range(5)] >>> for multiplier in create_multipliers(): ... print multiplier(2) ...
你期望的結果是:
<code>0 2 4 6 8</code>
實際上:
<code>8 8 8 8 8</code>
是不是非常吃驚!出現這種情況主要是因為Python的後期繫結行為,該變數在閉包中使用的同時,內部函式又在呼叫它。
解決方案:
>>> 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
7.建立迴圈模組依賴關係
假設有兩個檔案,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:
<code>>>> import a 1</code>
可以很好地工作,也許你會感到驚訝。畢竟,我們確實在這裡做了一個迴圈匯入,難道不應該有點問題嗎?
僅僅存在一個迴圈匯入並不是Python本身問題,如果一個模組被匯入,Python就不會試圖重新匯入。根據這一點,每個模組在試圖訪問函式或變數時,可能會在執行時遇到些問題。
當我們試圖匯入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,這樣就要呼叫f(),並且試圖訪問b.x。但是b.x並未被定義。
可以這樣解決,僅僅修改b.py匯入到a.py中的g()函式:
x = 1 def g(): import a # This will be evaluated only when g() is called print a.f()
無論何時匯入,一切都可以正常執行:
>>> 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擁有非常豐富的模組庫,並且支援“開箱即用”。因此,如果不刻意避免,很容易發生命名衝突事件。例如,在你的程式碼中可能有一個email.py的模組,由於名稱一致,它很有可能與Python自帶的標準庫模組發生衝突。
9.未按規定處理Python2.x和Python3.x之間的區別
看一下foo.py:
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
解決方案:
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()
在Py3k中執行結果:
<code>$ python3 foo.py 1 key error 1 $ python3 foo.py 2 value error 2</code>
在 Python招聘指南裡有許多關於Python 2與Python 3在移植程式碼時需要關注的注意事項與討論,大家可以前往看看。
10.濫用__del__方法
比如這裡有一個叫mod.py的檔案:
import foo class Bar(object): ... def __del__(self): foo.cleanup(self.myhandle)
下面,你在another_mod.py檔案裡執行如下操作:
import mod mybar = mod.Bar()
你會獲得一個AttributeError異常。
至於為什麼會出現該異常,點選這裡檢視詳情。當直譯器關閉時,該模組的全域性變數全部設定為None。因此,在上面這個例子裡,當__del__被呼叫時,foo已經全部被設定為None。
一個很好的解決辦法是使用atexit.register()代替。順便說一句,當程式執行完成後,您註冊的處理程式會在直譯器關閉之前停止 工作。
修復上面問題的程式碼:
import foo import atexit def cleanup(handle): foo.cleanup(handle) class Bar(object): def __init__(self): ... atexit.register(cleanup, self.myhandle)
在程式的正常終止的前提下,這個實現提供了一個整潔可靠的方式呼叫任何需要清理的功能。
總結
Python是一款強大而靈活的程式語言,並且帶有許多機制和模式來大大提高工作效率。正如任何一門語言或軟體工具一樣,人們對其能力都會存在一個限制性地理解或欣賞,有些是弊大於利,有些時候反而會帶來一些陷進。 體會一名語言的細微之處,理解一些常見的陷阱,有助於你在開發者的道路上走的更遠。
來自: toptal
相關文章
- python開發者常犯的10個錯誤Python
- Python開發人員常犯的幾個重大錯誤Python
- Java 開發者最容易犯的10個錯誤Java
- 【譯】Go 專案開發裡最常犯的 10 個錯誤Go
- 使用者研究過程中常犯的10個錯誤
- 十個PHP開發者最容易犯的錯誤PHP
- Python 初學者常犯的5個錯誤,布林型竟是整型的子類Python
- 【盤點】Python新手入門常犯的錯誤!Python
- java開發管理者們常犯之錯誤與解決辦法Java
- (網頁)Java程式設計師們最常犯的10個錯誤(轉)網頁Java程式設計師
- 前端開發最容易犯的13個JavaScript錯誤前端JavaScript
- 編寫 SQL 程式碼時常犯的九個錯誤SQL
- 使用 Kubernetes 最容易犯的 10 個錯誤!
- 企業選型作業上常犯的一個錯誤
- 使用 @Transactional 時常犯的N種錯誤
- 10個資料科學家常犯的程式設計錯誤(附解決方案)資料科學程式設計
- 開發新手最容易犯的50個 Ruby on Rails 錯誤(1)AI
- 真人踩過的坑,告訴你避免自動化測試新手常犯的10個錯誤
- 使用 Spring Framework 時常犯的十大錯誤SpringFramework
- Golang開發常見的57個錯誤Golang
- 產品經理常犯的十大頂級錯誤
- 程式設計師準備面試時常犯11個錯誤,切記!程式設計師面試
- Python最容易犯的錯誤,一定要警惕!Python
- [譯] 我在程式設計初級階段常犯的錯誤程式設計
- 學習Python最容易犯的錯誤,這10條一定要記住!Python
- 資料分析中會常犯哪些錯誤,如何解決?
- 三個最火的Python Web開發框架PythonWeb框架
- 開發者談APP Store應用最佳化的五個普遍錯誤方式APP
- 寫給go開發者的gRPC教程-錯誤處理GoRPC
- Python新手入門最容易犯的錯誤有哪些?Python
- Java 開發最容易寫的 10 個bugJava
- 從渲染流程解說Flutter老鳥也常犯的錯誤——多次重建Flutter
- 總結一些,書寫 CSS 的時候,經常犯的錯誤!CSS
- 程式碼歷史上最昂貴的 7 個錯誤
- 我作為開發者犯過的兩次愚蠢的錯誤
- 10款最受Python開發者歡迎的Python IDEPythonIDE
- 使用Python時常見的9個錯誤Python
- JPA 開發中遇到的錯誤
- 【熱點】數字化轉型最致命的4個誤區和3個錯誤