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.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
>>> import a
1
到此,沒有出現異常,也許這個會給你帶來驚喜,畢竟,我們這里有一個循環(huán)導入的問題,大概應該是一個問題,不應該?答案是,僅僅存在循環(huán)導入本身并不是Python的一個問題。如果已導入的模塊,Python足夠聰明,不會嘗試重新導入它。但是根據每個模塊嘗試訪問另一個模塊中定義的函數或變量,你可能會遇到一些問題。
所以回到例子中,當我們導入a.py
,它導入b.py
有沒有問題?因為b.py
不需要從a.py
中導入任何變量,這是因為唯一調用的a.f()
還是在調用g()
時被調用,所以此時a.py
或b.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,但是你明白了。