Python開發(fā)人員犯下的10個最常見的錯誤

1 濫用表達式作為函數參數的默認值

python允許通過為函數提供默認值來指定函數參數的,但是當默認值是可變的時,就會產生一些問題:

def foo(bar=[]):
    bar.append('baz')
    return bar

上面的代碼中,期望的是 foo() 重復調用(即不指定bar參數)將始終返回 'baz' ,因此假設每次 foo() 調用 bar 被設置為 []

但是,讓我們來看看執(zhí)行次操作時實際發(fā)生的情況:

>>> foo()
['baz']
>>> foo()
['baz', 'baz']

咦,為什么每次調用都會默認值附加 'baz' 到現有的列表中,而不是每次都創(chuàng)建一個新列表?

答案就是: 函數參數的默認值僅在定義函數時計算一次。因此 bar 僅在 foo() 首次定義時將參數初始化為其默認值,但隨后調用 foo() (即未指定bar參數),將繼續(xù)使用 bar 最初初始化的相同列表。

僅供參考,一個常見的解決方法如下:

def foo(bar=None):
    if not bar:
        bar = []
    bar.append('baz')
    return bar

2 錯誤的使用類變量

請考慮一下示例:

>>> class A(object):
...     x = 1
... 
>>> class B(A):
...     pass
... 
>>> class C(A):
...     pass
... 
>>> print(A.x, B.x, C.x)
1 1 1

以上的輸出是沒有問題的,請繼續(xù)往下看:

>>> 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)的方法,因此在上面的代碼中,由于在C中找不到x屬性,因此將在其基類中查找它。換句話數,C沒有自己的x屬性,因此引用C.x實際上值得是A.x。

3 錯誤的為異常塊指定參數

假如你用一下代碼:

# 這段代碼是python 2.7版本的
>>> 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語句沒有采用這種方式指定的異常列表,相反,在python2.x中,語法 except Exception, e 用于將異常綁定到指定的可選的第二個參數(本例中e),以用于進一步檢查。結果在上面的代碼中,IndexError異常沒有被except語句捕獲,相反,異常最終被綁定到一個名為IndexError的參數。

except語句中,捕獲多個異常的正確方法是將第一個參數指定為包含要捕獲所有異常的元祖,此外為了獲得最大的可移植性,請使用as關鍵字,因為Python2和Python3都支持該語法。

>>> try:
...     l = ['a', 'b']
...     int(l[2])
... except (ValueError, IndexError) as e:
...     pass
... 
>>> 

4 誤解Python范圍規(guī)范

Python范圍解析是基于所謂的LEGB規(guī)則。在Python的工作方式中有一些細微之處,讓我們看看常見的更高級的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會自動將變量視為該作用域的本地變量,并在任何外部作用域中隱藏任何類似命名的變量。

但在使用列表時,有一個特殊的現象,請看以下代碼示例:

>>> 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 foo2
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]
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
IndexError: list index out of range

在迭代時,從列表或數組中刪除項是Python常見的問題。幸運的是Python結合許多優(yōu)雅的編程范例,如果使用得當可以簡化代碼。另外一個好處是更簡單的代碼不太可能被意外刪除列表項而導致迭代問題。它完美的工作:

>>> 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調用內部函數時,閉包中使用的變量值是后期綁定行為導致的。所以上面的代碼中,每當調用任何返回的函數時,在調用i它時,在周圍的作用域中查找值,那是循環(huán)已經完成,因此i已經分配了它的最終值4。

這個常見問題的解決是有點像黑客的做法:

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

這里利用了默認參數來生成匿名函數,以實現所需的行為,有些人稱之為優(yōu)雅,有些人會認為微免,有些人會討厭它。但是作為Python的開發(fā)人員,無論如何都要理解它。

7 創(chuàng)建循環(huán)引用

假設你有兩個文件,a.pyb.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

到此,沒有出現異常,也許這個會給你帶來驚喜,畢竟,我們這里有一個循環(huán)導入的問題,大概應該是一個問題,不應該?答案是,僅僅存在循環(huán)導入本身并不是Python的一個問題。如果已導入的模塊,Python足夠聰明,不會嘗試重新導入它。但是根據每個模塊嘗試訪問另一個模塊中定義的函數或變量,你可能會遇到一些問題。

所以回到例子中,當我們導入a.py,它導入b.py有沒有問題?因為b.py 不需要從a.py中導入任何變量,這是因為唯一調用的a.f()還是在調用g()時被調用,所以此時a.pyb.py中沒有任何內容調用g(),所以一切看起來是美好的。

如果我們嘗試導入b.py,看看會發(fā)生什么?(前提是沒有先導入a.py)

>>> import b
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/wen/b.py", line 1, in <module>
    import a
  File "/home/wen/a.py", line 8, in <module>
    print(f())
  File "/home/wen/a.py", line 5, in f
    return b.x
AttributeError: module 'b' has no attribute 'x'
>>>

這就出現問題了。 在導入b.py中,他會嘗試導入a.py,而后者又會調用f()嘗試訪問的內容b.x,但b.x尚未定義,因此出現AttributeError問題。

這里提供一個簡單的方案處理這個問題,只需要修改b.py,在g()中導入a.py

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

當我們導入它時,一切都會變得美好:

>>> import b
>>> b.g()
1
1

8 名稱與Python標準庫模塊沖突

Python的優(yōu)點之一是它提供了“開箱即用”的豐富的庫模塊。但是如果你沒有意識的避開它,那么在發(fā)成自定義模塊與Python標準庫模塊沖突的幾率會增大很多。

9 未能解決Python2和Python3之間的差異

考慮一下文件 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()

在Python2上,正常運行:

$ python foo.py 1
key error
1
$ python foo.py 2
value error
2

但是在Python3上:

$ 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塊的范圍是不可訪問的。(原因是,否則,它會在內存中保持堆棧幀的引用循環(huán),直到垃圾收集器運行并從內存中清除引用。

避免此問題的一種方法是在塊的范圍之外維護對異常對象的引用,以except使其保持可訪問狀態(tài)。這是使用此技術的上一個示例的一個版本,從而產生兼容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()

在Python3上運行:

$ 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(slef.myhandle())

然后你試著這樣做 another_mod.py:

import mod
mybar = mod.Bar()

你會得到一個丑陋的AttributeError

當解釋器關閉時,模塊的全局變量都被設置為None。因此,在上面的示例中,在__del__調用的位置,名稱foo已設置為None

解決方案是使用atexit.register()。這樣,當您的程序完成執(zhí)行時(正常退出時),您的注冊處理程序將在解釋器關閉之前啟動:

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,但是你明白了。

原文

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,746評論 2 370

推薦閱讀更多精彩內容

  • 摘要:Python是一門簡單易學的編程語言,語法簡潔而清晰,并且擁有豐富和強大的類庫。在日常開發(fā)中,開發(fā)者很容犯一...
    山禾家的貓閱讀 323評論 0 0
  • 〇、前言 本文共108張圖,流量黨請慎重! 歷時1個半月,我把自己學習Python基礎知識的框架詳細梳理了一遍。 ...
    Raxxie閱讀 18,987評論 17 410
  • 五月,這個季節(jié)是屬于您的 薔薇、矢車菊與康乃馨 陽光在花瓣上蕩著秋千 一些歡笑從花香里溢出 不,所有的季節(jié)都是屬于...
    一地月光閱讀 325評論 0 3
  • 愛也是一種并非與生俱來的能力,我們將用一生的時間學會如何去愛。 就像《小王子》中的玫瑰和狐貍,我們被教會了如何去愛...
    慕堯VIVIENNNE閱讀 433評論 0 8