Python程式設計師最常犯的10個錯誤

2021-03-02 藍橋雲課精選
常見錯誤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.x1 1 1

這個結果很正常。

>>> B.x = 2>>> print A.x, B.x, C.x1 2 1

嗯,結果和預計的一樣。

>>> A.x = 3>>> print A.x, B.x, C.x3 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 fooUnboundLocalError: 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 fooUnboundLocalError: 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)...

你可能覺得輸出結果應該是這樣的:

02468

但是,實際的輸出結果卻是:

88888

嚇了一跳吧!

這個結果的出現,主要是因為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)...02468

請注意!我們在這裡利用了默認參數來實現這個lambda匿名函數。有人可能認為這樣做很優雅,有人會覺得很巧妙,還有人會嗤之以鼻。但是,如果你是一名Python程式設計師,不管怎樣你都應該要了解這種解決方法。

常見錯誤7:模塊之間出現循環依賴(circular dependencies)

假設你有兩個文件,分別是a.py和b.py,二者相互引用,如下所示:

a.py文件中的代碼:

import bdef f():    return b.xprint f()

b.py文件中的代碼:

import ax = 1def g():    print a.f()

首先,我們嘗試導入a.py模塊:

>>> import a1

代碼運行正常。也許這出乎了你的意料。畢竟,我們這裡存在循環引用這個問題,想必應該是會出現問題的,難道不是嗎?

答案是,僅僅存在循環引用的情況本身並不會導致問題。如果一個模塊已經被引用了,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 bTraceback (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.xAttributeError: '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 = 1def 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 end1 # 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 sysdef 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 1key error1$ python foo.py 2value error2

但是現在,我們換成Python 3再運行一遍:

$ python3 foo.py 1key errorTraceback (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 sysdef 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 1key error1$ python3 foo.py 2value error2

太棒了!

常見錯誤10:錯誤使用del方法

假設你在mod.py的文件中編寫了下面的代碼:

import fooclass Bar(object):        ...    def __del__(self):        foo.cleanup(self.myhandle)

之後,你在another_mod.py文件中進行如下操作:

import modmybar = mod.Bar()

如果你運行another_mod.py模塊的話,將會出現AttributeError異常。

為什麼?因為當解釋器結束運行的時候,該模塊的全局變量都會被設置為None。因此,在上述示例中,當__del__方法被調用之前,foo已經被設置成了None。

要想解決這個有點棘手的Python編程問題,其中一個辦法就是使用atexit.register()方法。這樣的話,當你的程序執行完成之後(即正常退出程序的情況下),你所指定的處理程序就會在解釋器關閉之前運行。

應用了上面這種方法,修改後的mod.py文件可能會是這樣子的:

import fooimport atexitdef 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大常見錯誤,將會幫助你有效地使用這門語言,同時也能避免犯一些比較常見的錯誤。

轉載自:http://codingpy.com/article/top-10-mistakes-that-python-programmers-make

原文連結:http://www.toptal.com/python/top-10-mistakes-that-python-programmers-make

譯者:EarlGrey@編程派


相關焦點

  • Python程式設計師最常犯的10個錯誤,你中招了嗎?
    鑑於此,本文列出了Python開發人員常犯的10個小錯誤,資深程序猿也難免會中招哦。事實上,Python的這種工作方式較為獨特,會導致一些編程錯誤,例如:>>> x = 10>>> def foo():...
  • Python程式設計師的30個常見錯誤
    文章中,我將總結新老Python程式設計師常犯的一些錯誤,以幫助你們在自己的工作避免犯同樣或類似錯誤。本文引用地址:http://www.eepw.com.cn/article/201901/396507.htm  首先我要說明一下的是,這些都是來源於第一手的經驗。
  • JavaScript 最容易犯的 10 個錯誤
    為了回饋社區,codeburst 從他們公司的上千個項目中,整理了 JavaScript 出現次數最多的 10 個錯誤。如果能避免這些錯誤的話,那麼你能成為一個更好的程式設計師。以下就是排名最高的 10 個 錯誤:Uncaught TypeError: Cannot read property這個錯誤大家應該經常碰到,當你在 Chrome 瀏覽器中,從一個 undefined 的對象上嘗試讀取一個屬性或者調用一個方法的時候,就會拋出這個錯誤:
  • 程式設計師最愛的13個免費Python課程
    它是網上最棒的Python課程之一。話不多說,以下便是筆者最喜歡的13個免費Python編程課,初學者和中級Python程式設計師均可學習。這些課程不止包含基礎編程課,還有用Python來處理OOP和相關數據科學工作。1. Python核心及高級課程這是一門學習Python的綜合性課程,應該感謝老師一直免費提供。
  • Python 初學者常犯的5個錯誤,布爾型竟是整型的子類
    在本文中,作者列舉了 5 個初學者常犯的錯誤,希望它們能幫助初學者寫更加正確與優美的代碼。Python 中的預設參數會在執行函數定義時計算一次,這表示在函數完成定義後該表達式只執行一次,因此預設值可以用於後續的每一次調用。如果我們令預設參數為可變的,例如列表或字典等,那麼對於將來所有的調用,該參數都是一直保留且可變的。
  • Python新手入門最容易犯的錯誤匯總
    10)在 for循環語句中忘記調用 len()(導致「TypeError: 『list』 object cannot be interpreted as an integer」)通常你想要通過索引來迭代一個
  • 10個常犯的期刊投稿合規性錯誤
    這篇文章中,我們將分享十種作者常犯的導致拒稿的期刊合規性錯誤:1.未能遵守期刊指南:這種情況主要是是因為作者在起草稿件時沒有檢查期刊指南,因此也就沒有遵守相應的期刊要求。2.數據不一致:稿件中的數據中有不一致之處,或者是數據的無意義重複。比較常見的是文章正文的數字與表格/圖表中的數字不匹配。
  • 盤點人們最常犯的10個英語語法錯誤
    Using data from millions of its subscribers, Microsoft recently rounded up a list of the top 10 grammar mistakes in the English language.
  • 新手爸媽常犯的10個錯誤,你中了幾個?
    初次迎接自己的寶寶的新手爸媽,想要給寶寶十分的呵護,可是往往因為一些常識性的問題,犯一些錯誤,而這些錯誤可能會影響寶寶的健康成長!今天,小編就總結了10個常犯的錯誤,希望能對新手爸媽有用哦~錯誤一:寶寶要比大人穿得多新生兒對自身溫度的調節能力沒有成人強,而且自身的散熱功能也不完善,因此,寧可少穿,也不要多穿哦。
  • 年輕父母教育孩子最容易犯的10個錯誤
    所有父母都會犯錯誤,新手爸媽在育兒方面尤其如此。美國「網絡醫學博士」網站最新載文,刊出由專家總結出的「新爸媽常犯的10個錯」,值得所有父母們重視。  1.遇到啥事都恐慌。當然,如果寶寶哭了1個小時也哄不好,且伴有發熱、皮疹和持續嘔吐,務必趕緊看醫生。  3.叫醒寶寶把奶喂。沃克博士表示,人們普遍錯誤地認為,母乳太稀,寶寶只喝母乳,夜裡一定會餓,得中途弄醒他們再餵次奶。但事實是,母乳絕對夠寶寶安心地睡一整晚,媽媽們大可放心。  4.溢奶嘔吐分不清。沃克博士表示,這兩者的區別主要在於頻率。
  • 致命錯誤!Python開發者的7個崩潰瞬間
    具有面向對象編程背景的開發人員容易忽略Python的慣用特性,很可能會濫用編程結構,從而產生不可預見且很難捕捉的錯誤。更糟糕的是,大多數錯誤很難發現,可能在後續工作中造成麻煩。下文匯總了程式設計師(尤其是新手)可能犯的常見錯誤,以及該如何糾正這些錯誤,編寫更好的、無錯誤的Python代碼。讓我們開始吧!
  • 一個go和python有趣的例子
    今天在一博文中看到一個有趣的問題,分享給大家,曾經也通過自學學習過python程式語言,python是一門很簡煉、功能豐富的語言,目前已大量用於科學研究和網際網路開發各個領域
  • 年度最受推薦的10本Python書籍
    >連結:https://hackr.io/blog/10-best-python-books-for-beginners-and-advanced-programmerspython是一種通用的解釋型編程,主要用於Web開發、機器學習和複雜數據分析。
  • [初級]Java開發人員最常犯的10個錯誤
    考慮下面使用原始類型List作為參數的代碼:public static void add(List list, Object o){    list.add(o);}public static void main(String[] args){    List<String> list = new ArrayList<String>();    add(list,
  • 新手常見的5個Python錯誤
    無論選擇哪種IDE,都可能會遇到一些錯誤。這些錯誤和問題的發生主要是由於判斷失誤,對特定主題缺乏深入的知識,或者僅僅可能是不小心導致的。本篇文章總結了一些初學者常犯的5種錯誤,這些錯誤雖然特別簡單,但是對於初學者來說可能會浪費比較多的時間在上面。而且由於每個人的編程習慣不同,錯誤狀態百出,有時候也不是能從網上直接搜索到錯誤原因。
  • 警惕:研究中最常犯的10大統計學錯誤
    2020年6月,PM&R雜誌發表了題為「Ten Common Statistical Errors from All Phases of Research, and Their Fixes」的文章,作者列舉了研究四個階段(研究設計→數據整理與清洗→數據分析→結果報告)常犯的10個統計學錯誤案例,並給出了可以幫助研究人員避免這些錯誤的潛在解決方案
  • Python入門最容易犯的錯誤
    來自:Python之禪(微信號:rmb10000e)Python 以其簡單易懂的語法格式與其它語言形成鮮明對比,初學者遇到最多的問題就是不按照 Python 的規則來寫,即便是有編程經驗的程式設計師導致:SyntaxError: invalid syntax2、使用錯誤的縮進Python用縮進區分代碼塊,常見的錯誤用法:print('Hello!')    print('Howdy!')導致:IndentationError: unexpected indent。
  • 人們常犯的24個邏輯錯誤!
    小紅犯了錯誤歸因的謬誤。你操作的感情可能包括恐懼、嫉妒、憐憫、驕傲等等。一個邏輯嚴謹的論述可能激起別人的情感波動,但是如果只用感情操作而不用邏輯論述,那你就犯了訴諸感情的錯誤。每個心智健康的人都會受感情影響,所以這種謬誤很有效,但這也是為什麼這種謬誤是低級和不誠實的手段。例子:小紅在飯店看到小明吃狗肉,於是上前訓斥:「你怎麼可以吃狗肉,小狗多麼可愛,就像小朋友一樣,你忍心傷害小朋友嗎?」
  • Python程式設計師用文字加密的方式,給女程式設計師寫情書,一周後牽手回家
    誰說程式設計師不浪漫,尤其是Python程式設計師,對文字加密後,寫情書只有特定的人才能看懂。 ,這樣即使情書被別人看到,也看不懂,只有那位女程式設計師才能破解密碼,被拒絕也不會丟面子。
  • 15個python小錯誤 !新手請注意
    python語言簡單易用,但容易給新入門的朋友造成一些微妙的,難以捕捉的錯誤,稍不注意就入坑了。