python程序員開發(fā)中常犯的10個(gè)錯(cuò)誤

字號(hào):


    python是一門簡(jiǎn)單易學(xué)的編程語言,語法簡(jiǎn)潔而清晰,并且擁有豐富和強(qiáng)大的類庫。與其它大多數(shù)程序設(shè)計(jì)語言使用大括號(hào)不一樣 ,它使用縮進(jìn)來定義語句塊。
    在平時(shí)的工作中,python開發(fā)者很容易犯一些小錯(cuò)誤,這些錯(cuò)誤都很容易避免,本文總結(jié)了python開發(fā)者最常犯的10個(gè)錯(cuò)誤,一起來看下,不知你中槍了沒有。
    1.濫用表達(dá)式作為函數(shù)參數(shù)默認(rèn)值
    python允許開發(fā)者指定一個(gè)默認(rèn)值給函數(shù)參數(shù),雖然這是該語言的一個(gè)特征,但當(dāng)參數(shù)可變時(shí),很容易導(dǎo)致混亂,例如,下面這段函數(shù)定義:
    代碼如下:
    >>> 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
    在上面這段代碼里,一旦重復(fù)調(diào)用foo()函數(shù)(沒有指定一個(gè)bar參數(shù)),那么將一直返回'bar',因?yàn)闆]有指定參數(shù),那么foo()每次被調(diào)用的時(shí)候,都會(huì)賦予[]。下面來看看,這樣做的結(jié)果:
    代碼如下:
    >>> 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.錯(cuò)誤地使用類變量
    先看下面這個(gè)例子:
    代碼如下:
    >>> 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中,類變量都是作為字典進(jìn)行內(nèi)部處理的,并且遵循方法解析順序(mro)。在上面這段代碼中,因?yàn)閷傩詘沒有在類c中發(fā)現(xiàn),它會(huì)查找它的基類(在上面例子中只有a,盡管python支持多繼承)。換句話說,就是c自己沒有x屬性,獨(dú)立于a,因此,引用 c.x其實(shí)就是引用a.x。
    3.為異常指定不正確的參數(shù)
    假設(shè)代碼中有如下代碼:
    代碼如下:
    >>> 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通常是用來綁定異常里的 第二參數(shù),好讓其進(jìn)行更進(jìn)一步的檢查。因此,在上面這段代碼里,indexerror異常并沒有被except語句捕獲,異常最后被綁定 到了一個(gè)名叫indexerror的參數(shù)上。
    在一個(gè)異常語句里捕獲多個(gè)異常的正確方法是指定第一個(gè)參數(shù)作為一個(gè)元組,該元組包含所有被捕獲的異常。與此同時(shí),使用as關(guān)鍵字來保證最大的可移植性,python 2和python 3都支持該語法。
    代碼如下:
    >>> try:
    ...     l = [a, b]
    ...     int(l[2])
    ... except (valueerror, indexerror) as e: 
    ...     pass
    ...
    >>>
    4.誤解python規(guī)則范圍
    python的作用域解析是基于legb規(guī)則,分別是local、enclosing、global、built-in。實(shí)際上,這種解析方法也有一些玄機(jī),看下面這個(gè)例子:
    代碼如下:
    >>> 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
    許多人會(huì)感動(dòng)驚訝,當(dāng)他們?cè)诠ぷ鞯暮瘮?shù)體里添加一個(gè)參數(shù)語句,會(huì)在先前工作的代碼里報(bào)unboundlocalerror錯(cuò)誤( 點(diǎn)擊這里查看更詳細(xì)描述)。
    在使用列表時(shí),開發(fā)者是很容易犯這種錯(cuò)誤的,看看下面這個(gè)例子:
    代碼如下:
    >>> 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運(yùn)行正常?
    答案與前面那個(gè)例子是一樣的,但又有一些微妙之處。foo1沒有賦值給lst,而foo2賦值了。lst += [5]實(shí)際上就是lst = lst + [5],試圖給lst賦值(因此,假設(shè)python是在局部作用域里)。然而,我們正在尋找指定給lst的值是基于lst本身,其實(shí)尚未確定。
    5.修改遍歷列表
    下面這段代碼很明顯是錯(cuò)誤的:
    代碼如下:
    >>> 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
    在遍歷的時(shí)候,對(duì)列表進(jìn)行刪除操作,這是很低級(jí)的錯(cuò)誤。稍微有點(diǎn)經(jīng)驗(yàn)的人都不會(huì)犯。
    對(duì)上面的代碼進(jìn)行修改,正確地執(zhí)行:
    代碼如下:
    >>> 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.如何在閉包中綁定變量
    看下面這個(gè)例子:
    代碼如下:
    >>> def create_multipliers():
    ...     return [lambda x : i * x for i in range(5)]
    >>> for multiplier in create_multipliers():
    ...     print multiplier(2)
    ...
    你期望的結(jié)果是:
    代碼如下:
    0
    2
    4
    6
    8
    實(shí)際上:
    代碼如下:
    8
    8
    8
    8
    8
    是不是非常吃驚!出現(xiàn)這種情況主要是因?yàn)閜ython的后期綁定行為,該變量在閉包中使用的同時(shí),內(nèi)部函數(shù)又在調(diào)用它。
    解決方案:
    代碼如下:
    >>> 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.創(chuàng)建循環(huán)模塊依賴關(guān)系
    假設(shè)有兩個(gè)文件,a.py和b.py,然后各自導(dǎo)入,如下:
    在a.py中:
    代碼如下:
    import b
    def f():
        return b.x
    print f()
    在b.py中:
    代碼如下:
    import a
    x = 1
    def g():
        print a.f()
    首先,讓我們?cè)囍鴮?dǎo)入a.py:
    代碼如下:>>> import a
    1
    可以很好地工作,也許你會(huì)感到驚訝。畢竟,我們確實(shí)在這里做了一個(gè)循環(huán)導(dǎo)入,難道不應(yīng)該有點(diǎn)問題嗎?
    僅僅存在一個(gè)循環(huán)導(dǎo)入并不是python本身問題,如果一個(gè)模塊被導(dǎo)入,python就不會(huì)試圖重新導(dǎo)入。根據(jù)這一點(diǎn),每個(gè)模塊在試圖訪問函數(shù)或變量時(shí),可能會(huì)在運(yùn)行時(shí)遇到些問題。
    當(dāng)我們?cè)噲D導(dǎo)入b.py會(huì)發(fā)生什么(先前沒有導(dǎo)入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'
    出錯(cuò)了,這里的問題是,在導(dǎo)入b.py的過程中還要試圖導(dǎo)入a.py,這樣就要調(diào)用f(),并且試圖訪問b.x。但是b.x并未被定義。
    可以這樣解決,僅僅修改b.py導(dǎo)入到a.py中的g()函數(shù):
    代碼如下:
    x = 1
    def g():
        import a # this will be evaluated only when g() is called
        print a.f()
    無論何時(shí)導(dǎo)入,一切都可以正常運(yùn)行:
    代碼如下:
    >>> 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標(biāo)準(zhǔn)庫模塊名稱沖突
    python擁有非常豐富的模塊庫,并且支持“開箱即用”。因此,如果不刻意避免,很容易發(fā)生命名沖突事件。例如,在你的代碼中可能有一個(gè)email.py的模塊,由于名稱一致,它很有可能與python自帶的標(biāo)準(zhǔn)庫模塊發(fā)生沖突。
    9.未按規(guī)定處理python2.x和python3.x之間的區(qū)別
    看一下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里面可以很好地運(yùn)行:
    代碼如下:
    $ 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中運(yùn)行結(jié)果:
    代碼如下:$ python3 foo.py 1
    key error
    1
    $ python3 foo.py 2
    value error
    2
    在 python招聘指南里有許多關(guān)于python 2與python 3在移植代碼時(shí)需要關(guān)注的注意事項(xiàng)與討論,大家可以前往看看。
    10.濫用__del__方法
    比如這里有一個(gè)叫mod.py的文件:
    代碼如下:
    import foo
    class bar(object):
            ...
        def __del__(self):
            foo.cleanup(self.myhandle)
    下面,你在another_mod.py文件里執(zhí)行如下操作:
    代碼如下:
    import mod
    mybar = mod.bar()
    你會(huì)獲得一個(gè)attributeerror異常。
    至于為什么會(huì)出現(xiàn)該異常,點(diǎn)擊這里查看詳情。當(dāng)解釋器關(guān)閉時(shí),該模塊的全局變量全部設(shè)置為none。因此,在上面這個(gè)例子里,當(dāng)__del__被調(diào)用時(shí),foo已經(jīng)全部被設(shè)置為none。
    一個(gè)很好的解決辦法是使用atexit.register()代替。順便說一句,當(dāng)程序執(zhí)行完成后,您注冊(cè)的處理程序會(huì)在解釋器關(guān)閉之前停止 工作。
    修復(fù)上面問題的代碼:
    代碼如下:
    import foo
    import atexit
    def cleanup(handle):
        foo.cleanup(handle)
    class bar(object):
        def __init__(self):
            ...
            atexit.register(cleanup, self.myhandle)
    在程序的正常終止的前提下,這個(gè)實(shí)現(xiàn)提供了一個(gè)整潔可靠的方式調(diào)用任何需要清理的功能。
    總結(jié):
    python是一款強(qiáng)大而靈活的編程語言,并且?guī)в性S多機(jī)制和模式來大大提高工作效率。正如任何一門語言或軟件工具一樣,人們對(duì)其能力都會(huì)存在一個(gè)限制性地理解或欣賞,有些是弊大于利,有些時(shí)候反而會(huì)帶來一些陷進(jìn)。 體會(huì)一名語言的細(xì)微之處,理解一些常見的陷阱,有助于你在開發(fā)者的道路上走的更遠(yuǎn)。