Python開發者最常犯的10個錯誤

 Python是一門簡單易學的編程語言,語法簡潔而清晰,而且擁有豐富和強大的類庫。與其它大多數程序設計語言使用大括號不同 ,它使用縮進來定義語句塊。php

  在平時的工做中,Python開發者很容易犯一些小錯誤,這些錯誤都很容易避免,本文總結了Python開發者最常犯的10個錯誤,一塊兒來看下,不知你中槍了沒有。html

 

  1.濫用表達式做爲函數參數默認值python

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

  解決方案:app

>>> 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.錯誤地使用類變量編程語言

  先看下面這個例子:ide

>>> 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是一款強大而靈活的編程語言,而且帶有許多機制和模式來大大提升工做效率。正如任何一門語言或軟件工具同樣,人們對其能力都會存在一個限制性地理解或欣賞,有些是弊大於利,有些時候反而會帶來一些陷進。 體會一名語言的細微之處,理解一些常見的陷阱,有助於你在開發者的道路上走的更遠。

相關文章
相關標籤/搜索