Python 程序员常见错误
-
與 for、while、try 搭配的 else語句
在 Python 中 else 除了與 if 匹配外,還可以與 for、while、try 語句匹配。
-
for/else
只有當 for 語句執行完畢的時候,else 才會執行。除非被 break 語句打斷
In [1]: for i in range(3):...: print i...: else:...: print "end"...: 0 1 2 endIn [2]: for i in range(3):...: print i...: if i == 2:...: break...: else:...: print "end"...: 0 1 2?
-
while/else
運行原理同 for/else
In [3]: i = 0In [4]: while i < 3:...: print i...: i += 1...: else:...: print "end"...: 0 1 2 endIn [6]: i = 0In [7]: while i < 3:...: print i...: i += 1...: if i == 1:...: break...: else:...: print "end"...: 0?
-
try/else
只有 try 語句中沒有發生異常時,else 才會執行。
In [8]: try:...: print "haha"...: except Exception as e:...: print "Error"...: else:...: print "end"...: haha end如果 try 語句中有異常,則 else 語句則不會執行。如果存在異常而 except 模塊沒捕獲到,那么 else 代碼塊中的代碼也不會執行。
In [9]: try:...: print "haha"...: raise Exception...: except Exception as e:...: print "Error"...: else:...: print "end"...: haha Error?
-
-
可變數據類型作為函數的默認值
In [10]: def fun(a, b=[]):...: b.append(a)...: print b...: In [11]: fun(3) [3]In [12]: fun(4) [3, 4]In [13]: fun(5) [3, 4, 5]為啥不是下面的這個呢?
[3] [4] [5]為什么呢?如你所見,每次都使用的是同一個列表,輸出為什么會是這樣?之所以得到這個結果,是因為在Python中,一個函數參數的默認值,僅僅在該函數定義的時候,被初始化一次。
在 Python 中,當我們編寫這樣的函數時,這個列表被實例化為函數定義的一部分。當函數運行時,它并不是每次都被實例化。這意味著,這個函數會一直使用完全一樣的列表對象,除非我們提供一個新的對象:
In [14]: fun(1, [2]) [2, 1]In [15]: fun(1, [3]) [3, 1]?
答案是一個函數參數的默認值,僅僅在該函數定義的時候,被賦值一次。如此,只有當函數 fun()第一次被定義的時候,才講參數 b 的默認值初始化到它的默認值(即一個空的列表)。當調用fun()的時候(不給參數 b),會繼續使用 b 最早初始化時的那個列表。
要想得到這樣的結果,正確的做法是:
In [16]: def fun(a, b=None):...: if not b: # or if b is None:...: b = []...: b.append(a)...: print b...: In [17]: fun(3) [3]In [18]: fun(4) [4]In [19]: fun(5) [5]?
-
可變類型作為類變量
class URLCatcher(object):urls = []def add_url(self, url):self.urls.append(url)這段代碼看起來非常正常。我們有一個儲存 URL 的對象。當我們調用 add_url 方法時,它會添加一個給定的 URL 到存儲中。看起來非常正確吧?讓我們看看實際是怎樣的:
a = URLCatcher() a.add_url('http://www.google.com')b = URLCatcher() b.add_url('http://www.bbc.co.hk')實際結果
# b.urls: ['http://www.google.com', 'http://www.bbc.co.uk']# a.urls: ['http://www.google.com', 'http://www.bbc.co.uk']等等,怎么回事?!我們想的不是這樣啊。我們實例化了兩個單獨的對象 a 和 b。把一個 URL 給了 a,另一個給了 b。這兩個對象怎么會都有這兩個 URL 呢?
這和第一個錯例是同樣的問題。創建類定義時,URL 列表將被實例化。該類所有的實例使用相同的列表。在有些時候這種情況是有用的,但大多數時候你并不想這樣做。你希望每個對象有一個單獨的儲存。為此,我們修改代碼為:
class URLCatcher(object):def __init__(self):self.urls = []def add_url(self, url):self.urls.append(url)現在,當創建對象時,URL 列表被實例化。當我們實例化兩個單獨的對象時,它們將分別使用兩個單獨的列表。
?
-
錯誤的使用類變量
In [1]: class A(object):...: x = 1...: In [2]: class B(A):...: pass...: In [3]: class C(A):...: pass...: In [4]: A.x, B.x, C.x Out[4]: (1, 1, 1)In [5]: B.x = 2In [6]: A.x, B.x, C.x Out[6]: (1, 2, 1)In [7]: A.x = 3In [8]: A.x, B.x, C.x Out[8]: (3, 2, 3)僅僅是改變了A.x,為什么C.x也跟著改變了。
當在 Python 值使用繼承的時候,也要注意類屬性的隱藏。對于一個類,可以通過類的 __ dict __ 屬性來查看所有的類屬性。
當通過類名來訪問一個類屬性的時候,會首先查找類的 __ dict __ 屬性,如果沒有找到類屬性,就會繼續查找父類。但是,如果子類定義了跟父類同名的類屬性后,子類的類屬性就會隱藏父類的類屬性。
在Python中,類變量都是作為字典進行內部處理的,并且遵循方法解析順序(MRO)。在上面這段代碼中,因為屬性x沒有在類C中發現,它會查找它的基類(在上面例子中只有A,盡管Python支持多繼承)。換句話說,就是C自己沒有x屬性,獨立于A,因此,引用 C.x其實就是引用A.x。
?
-
捕獲多個異常
In [10]: try:...: l = ["a", "b"]...: int(l[2])...: except ValueError, IndexError: # 想捕捉兩個異常...: print "error"...: --------------------------------------------------------------------------- IndexError Traceback (most recent call last) <ipython-input-10-6f6b12c012b2> in <module>()1 try:2 l = ["a", "b"] ----> 3 int(l[2])4 except ValueError, IndexError: # 想捕捉兩個異常5 print "error"IndexError: list index out of range這里的問題在于except語句不會像這樣去接受一系列的異常。并且,在Python 2.x里面,語法except Exception, e是用來將異常和這個可選的參數綁定起來(即這里的e),以用來在后面查看的。因此,在上面的代碼中,IndexError異常不會被except語句捕捉到;而最終ValueError這個異常被綁定在了一個叫做IndexError的參數上。
在except語句中捕捉多個異常的正確做法是將所有想要捕捉的異常放在一個元組(tuple)里并作為第一個參數給except語句。并且,為移植性考慮,使用as關鍵字,因為Python 2和Python 3都支持這樣的語法,例如:
In [11]: try:...: l = ["a", "b"]...: int(l[2])...: except (ValueError, IndexError) as e: # 想捕捉兩個異常...: print "error"...: print e...: error list index out of range?
-
誤解 Python 的作用域規則
Python的作用域解析是基于叫做LEGB(Local(本地),Enclosing(封閉),Global(全局),Built-in(內置))的規則進行操作的。
In [12]: x = 1 In [13]: def fun():...: x += 1...: print x...: In [14]: fun() --------------------------------------------------------------------------- UnboundLocalError Traceback (most recent call last) <ipython-input-14-69e6a439c52d> in <module>() ----> 1 fun() <ipython-input-13-d176b55edb95> in fun()1 def fun(): ----> 2 x += 13 print x4 UnboundLocalError: local variable 'x' referenced before assignment這是因為,在一個作用域里面給一個變量賦值的時候,Python****自動認為這個變量是這個作用域的本地變量,并屏蔽作用域外的同名的變量。
使用列表時同樣會存在這樣的問題
In [15]: a = [1, 2, 3] In [16]: def fun1():...: a.append(10)...: In [17]: fun1() In [18]: a Out[18]: [1, 2, 3, 10]In [19]: a = [1, 2, 3] In [20]: def fun2():...: a += [10]...: In [21]: fun2() --------------------------------------------------------------------------- UnboundLocalError Traceback (most recent call last) <ipython-input-21-32d138681f85> in <module>() ----> 1 fun2() <ipython-input-20-c050916b939b> in fun2()1 def fun2(): ----> 2 a += [10]3 UnboundLocalError: local variable 'a' referenced before assignment為什么 fun2 有問題,而 fun1 沒有問題?
答案和上一個例子一樣,但是更加不易察覺。fun1 并沒有給 a 賦值,但是 fun2 嘗試給 a 賦值。注意 a += [10] 只是 a = a + [10] 的簡寫,由此可以看到我們嘗試給 a 賦值(因此Python假設作用域為本地)。但是,這個要賦給 a 的值是基于 a 本身的(這里的作用域仍然是本地),而 a 卻沒有被定義,這就出錯了。
?
-
在遍歷列表的同時又通過下標修改列表
來看個例子
In [27]: aa = [1,2,3,4,5] In [28]: for i, v in enumerate(aa):...: if i % 2 == 0: # 本想刪除下標為偶數位的值...: aa.pop(i)...: In [29]: aa Out[29]: [2, 3, 5]遍歷一個列表或者數組的同時又刪除里面的元素,對任何有經驗的軟件開發人員來說這是個很明顯的錯誤。
?
-
與 Python 標準庫模塊命名沖突
Python的一個優秀的地方在于它提供了豐富的庫模塊。但是這樣的結果是,如果你不下意識的避免,很容易你會遇到你自己的模塊的名字與某個隨Python附帶的標準庫的名字沖突的情況(比如,你的代碼中可能有一個叫做email.py的模塊,它就會與標準庫中同名的模塊沖突)。
這會導致一些很粗糙的問題,例如當你想加載某個庫,這個庫需要加載Python標準庫里的某個模塊,結果呢,因為你有一個與標準庫里的模塊同名的模塊,這個包錯誤的將你的模塊加載了進去,而不是加載Python標準庫里的那個模塊。這樣一來就會有麻煩了。
?
-
元組中含有列表時,列表中的元素是可變的
In [30]: aa = (1, 2, 3, [4, 5, 6]) In [31]: id(aa) Out[31]: 61949896L In [32]: id(aa[3]) Out[32]: 62077384LIn [34]: aa[3].extend([10]) In [35]: aa Out[35]: (1, 2, 3, [4, 5, 6, 10])?
-
深淺拷貝
當使用下面的操作的時候,會產生淺拷貝的效果:
- 使用切片[:]操作
- 使用工廠函數(如list/dir/set)
- 使用copy模塊中的copy()函數
?
-
模塊循環依賴
在編碼的過程中就應當避免循環依賴的情況,或者代碼重構的過程中消除循環依賴。如果出現循環依賴時,常用的解決辦法就是把引用關系搞清楚,讓某個模塊在真正需要的時候再導入(一般放到函數里面)
?
-
x += y 與 x = x + y
一般來說,二者是等價的,至少看起來是等價的。
In [36]: x=1;x += 1;print x 2 In [37]: x=1;x = x+1;print x 2 In [38]: x=[1];x+=[2];print x [1, 2] In [39]: x=[1];x=x+[2];print x [1, 2]我們可以看下其 id 值
In [40]: x=[1];print id(x);x+=[2];print id(x) 61650568 61650568In [41]: x=[1];print id(x);x=x+[2];print id(x) 61652040 61651528可以看到 += 是在原有對象上進行修改,后面一種 + 其實是一個新的對象。
擴展:列表的 append 和 extend 方法也是在原有列表上進行操作的。
?
-
生成器不會保留迭代過后的值
In [42]: g = (x for x in range(5))In [43]: 3 in g Out[43]: TrueIn [44]: 4 in g Out[44]: TrueIn [45]: 1 in g Out[45]: False為什么 1 in g 會返回 False 呢? 因為在調用 3 in g,4 in g 后 1 已經不再迭代器里面了。
可以使用如下方法保存對應的值。
In [46]: g = (x for x in range(5))In [47]: aa = list(g)In [48]: aa Out[48]: [0, 1, 2, 3, 4]?
-
lambda 在閉包中會保存局部變量
In [67]: vv = [lambda: i for i in range(5)]In [68]: for f in vv:...: print f()...: 4 4 4 4 4當賦值給 vv 的時候,lambda 表達式會執行 i 循環,直到 i = 4 的時候保留。
可以使用生成器替換,但是也只能調用一次,代碼如下:
In [69]: vv = (lambda: i for i in range(5))In [70]: for f in vv:...: print f()...: 0 1 2 3 4In [71]: for f in vv:...: print f()...:?
-
拷貝可變對象
In [72]: a = [[1,2]] * 2In [73]: a Out[73]: [[1, 2], [1, 2]]In [74]: a[1][0] = 'a' # 只修改其中的一項In [75]: a Out[75]: [['a', 2], ['a', 2]] # 兩項都被修改了使用下面的方法可以避免這種情況發生
In [76]: b = [[1,2] for i in range(2)]In [77]: b Out[77]: [[1, 2], [1, 2]]In [78]: b[1][0] = 'a'In [79]: b Out[79]: [[1, 2], ['a', 2]]?
-
待續
參考:
linux.cn/article-8780-1.html
http://blog.jobbole.com/68256/
https://mp.weixin.qq.com/s?__biz=MzI5OTM0NjQxMQ==&mid=2247483877&idx=1&sn=656a76280a0e037bd8ce64ba874f9fd1&scene=19#wechat_redirect
https://mp.weixin.qq.com/s?__biz=MjM5MzgyODQxMQ==&mid=2650367466&idx=1&sn=20fc603007296ae645f6b4c7f3ca6c5e&chksm=be9cdebe89eb57a83034c012d66269759f61ddd42449c0d965ad006a2d736c34aa45cbeeef60&mpshare=1&scene=23&srcid=111819TrFjHK3MbmxNNUUY7M#rd
總結
以上是生活随笔為你收集整理的Python 程序员常见错误的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 猫爪草多少钱一斤?
- 下一篇: Python 函数不定长参数