亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 操作系統 > 正文

Python程序員的10個常見錯誤

2024-06-28 16:04:10
字體:
來源:轉載
供稿:網友

關于Python

Python是一門解釋性的,面向對象的,并具有動態語義的高級編程語言。它高級的內置數據結構,結合其動態類型和動態綁定的特性,使得它在快速應用程序開發(Rapid application Development)中頗為受歡迎,同時Python還能作為腳本語言或者膠水語言講現成的組件或者服務結合起來。Python支持模塊(modules)和包(packages),所以也鼓勵程序的模塊化以及代碼重用。

關于本文

Python簡單、易學的語法可能會誤導一些Python程序員(特別是那些剛接觸這門語言的人們),可能會忽略某些細微之處和這門語言的強大之處。

考慮到這點,本文列出了“十大”甚至是高級的Python程序員都可能犯的,卻又不容易發現的細微錯誤。(注意:本文是針對比《Python程序員常見錯誤》稍微高級一點讀者,對于更加新手一點的Python程序員,有興趣可以讀一讀那篇文章)

常見錯誤1:在函數參數中亂用表達式作為默認值

Python允許給一個函數的某個參數設置默認值以使該參數成為一個可選參數。盡管這是這門語言很棒的一個功能,但是這當這個默認值是可變對象(mutable)時,那就有些麻煩了。例如,看下面這個Python函數定義:

Python
123>>> def foo(bar=[]):        # bar是可選參數,如果沒有指明的話,默認值是[]...    bar.append("baz")    # 但是這行可是有問題的,走著瞧…...    return bar

人們常犯的一個錯誤是認為每次調用這個函數時不給這個可選參數賦值的話,它總是會被賦予這個默認表達式的值。例如,在上面的代碼中,程序員可能會認為重復調用函數foo() (不傳參數bar給這個函數),這個函數會總是返回‘baz’,因為我們假定認為每次調用foo()的時候(不傳bar),參數bar會被置為[](即,一個空的列表)。

那么我們來看看這么做的時候究竟會發生什么:

Python
123456>>>foo()["baz"]>>>foo()["baz","baz"]>>>foo()["baz","baz","baz"]

嗯?為什么每次調用foo()的時候,這個函數總是在一個已經存在的列表后面添加我們的默認值“baz”,而不是每次都創建一個新的列表?

答案是一個函數參數的默認值,僅僅在該函數定義的時候,被賦值一次。如此,只有當函數foo()第一次被定義的時候,才講參數bar的默認值初始化到它的默認值(即一個空的列表)。當調用foo()的時候(不給參數bar),會繼續使用bar最早初始化時的那個列表。

由此,可以有如下的解決辦法:

Python
123456789101112>>> def foo(bar=None):...    if bar is None: # 或者用 if not bar:...        bar = []...    bar.append("baz")...    return bar...>>> foo()["baz"]>>> foo()["baz"]>>> foo()["baz"]

常見錯誤2:不正確的使用類變量

看下面一個例子:

Python
1234567891011>>>classA(object):...    x=1...>>>classB(A):...    pass...>>>classC(A):...    pass...>>>PRintA.x,B.x,C.x111

看起來沒有問題。

Python
123>>> B.x = 2>>> print A.x, B.x, C.x1 2 1

嗯哈,還是和預想的一樣。

Python
123>>>A.x=3>>>printA.x,B.x,C.x323

我了個去。只是改變了A.x,為啥C.x也變了?

在Python里,類變量通常在內部被當做字典來處理并遵循通常所說的方法解析順序(Method Resolution Order (MRO))。因此在上面的代碼中,因為屬性x在類C中找不到,因此它會往上去它的基類中查找(在上面的例子中只有A這個類,當然Python是支持多重繼承(multiple inheritance)的)。換句話說,C沒有它自己獨立于A的屬性x。因此對C.x的引用實際上是對A.x的引用。(B.x不是對A.x的引用是因為在第二步里B.x=2將B.x引用到了2這個對象上,倘若沒有如此,B.x仍然是引用到A.x上的。——譯者注)

常見錯誤3:在異常處理時錯誤的使用參數

假設你有如下的代碼:

Python
123456789>>> try:...     l = ["a", "b"]...     int(l[2])... except ValueError, IndexError:  # 想捕捉兩個異常...     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是用來將異常和這個可選的參數綁定起來(即這里的e),以用來在后面查看的。因此,在上面的代碼中,IndexError異常不會被except語句捕捉到;而最終ValueError這個異常被綁定在了一個叫做IndexError的參數上。

在except語句中捕捉多個異常的正確做法是將所有想要捕捉的異常放在一個元組(tuple)里并作為第一個參數給except語句。并且,為移植性考慮,使用as關鍵字,因為Python 2和Python 3都支持這樣的語法,例如:

Python
1234567>>>try:...    l=["a","b"]...    int(l[2])...except(ValueError,IndexError)ase:  ...    pass...>>>

常見錯誤4:誤解Python作用域的規則

Python的作用域解析是基于叫做LEGB(Local(本地),Enclosing(封閉),Global(全局),Built-in(內置))的規則進行操作的。這看起來很直觀,對吧?事實上,在Python中這有一些細微的地方很容易出錯??催@個例子:

Python
12345678910>>> 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 fooUnboundLocalError: local variable 'x' referenced before assignment

這是怎么回事?

這是因為,在一個作用域里面給一個變量賦值的時候,Python自動認為這個變量是這個作用域的本地變量,并屏蔽作用域外的同名的變量。

很多時候可能在一個函數里添加一個賦值的語句會讓你從前本來工作的代碼得到一個UnboundLocalError。(感興趣的話可以讀一讀這篇文章。)

在使用列表(lists)的時候,這種情況尤為突出??聪旅孢@個例子:

Python
1234567891011121314151617>>>lst=[1,2,3]>>>deffoo1():...    lst.append(5)  # 這沒有問題......>>>foo1()>>>lst[1,2,3,5] >>>lst=[1,2,3]>>>deffoo2():...    lst+=[5]      # ... 這就有問題了!...>>>foo2()Traceback(mostrecentcalllast):  File"<stdin>",line1,in<module>  File"<stdin>",line2,infooUnboundLocalError:localvariable'lst'referencedbeforeassignment

嗯?為什么foo2有問題,而foo1沒有問題?

答案和上一個例子一樣,但是更加不易察覺。foo1并沒有給lst賦值,但是foo2嘗試給lst賦值。注意lst+=[5]只是lst=lst+[5]的簡寫,由此可以看到我們嘗試給lst賦值(因此Python假設作用域為本地)。但是,這個要賦給lst的值是基于lst本身的(這里的作用域仍然是本地),而lst卻沒有被定義,這就出錯了。

常見錯誤5:在遍歷列表的同時又在修改這個列表

下面這個例子中的代碼應該比較明顯了:

Python
123456789>>> 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集成了一些優雅的編程范式,如果使用得當,可以寫出相當簡化和精簡的代碼。一個附加的好處是更簡單的代碼更不容易遇到這種“不小心在遍歷列表時刪掉列表元素”的bug。例如列表推導式(list comprehensions)就提供了這樣的范式。再者,列表推導式在避免這樣的問題上特別有用,接下來這個對上面的代碼的重新實現就相當完美:

Python
12345>>>odd=lambdax:bool(x%2)>>>numbers=[nforninrange(10)]>>>numbers[:]=[nforninnumbersifnotodd(n)]  # 啊,這多優美>>>numbers[0,2,4,6,8]

常見錯誤6:搞不清楚在閉包(closures)中Python是怎樣綁定變量的

看這個例子:

Python
12345>>> def create_multipliers():...     return [lambda x : i * x for i in range(5)]>>> for multiplier in create_multipliers():...     print multiplier(2)...

期望得到下面的輸出:

Python
1234502468

但是實際上得到的是:

Python
1234588888

意外吧!

這是由于Python的后期綁定(late binding)機制導致的,這是指在閉包中使用的變量的值,是在內層函數被調用的時候查找的。因此在上面的代碼中,當任一返回函數被調用的時候,i的值是在它被調用時的周圍作用域中查找(到那時,循環已經結束了,所以i已經被賦予了它最終的值4)。

解決的辦法比較巧妙:

Python
1234567891011>>>defcreate_multipliers():...    return[lambdax,i=i:i*xforiinrange(5)]...>>>formultiplierincreate_multipliers():...    printmultiplier(2)...02468

這下對了!這里利用了默認參數去產生匿名函數以達到期望的效果。有人會說這很優美,有人會說這很微妙,也有人會覺得反感。但是如果你是一名Python程序員,重要的是能理解任何的情況。

常見錯誤7:循環加載模塊

假設你有兩個文件,a.py和b.py,在這兩個文件中互相加載對方,例如:

在a.py中:

Python
1234import bdef f():    return b.xprint f()

在b.py中:

Python
1234importax=1defg():    printa.f()

首先,我們試著加載a.py:

Python
12>>> import a1

沒有問題。也許讓人吃驚,畢竟有個感覺應該是問題的循環加載在這兒。

事實上在Python中僅僅是表面上的出現循環加載并不是什么問題。如果一個模塊以及被加載了,Python不會傻到再去重新加載一遍。但是,當每個模塊都想要互相訪問定義在對方里的函數或者變量時,問題就來了。

讓我們再回到之前的例子,當我們加載a.py時,它再加載b.py不會有問題,因為在加載b.py時,它并不需要訪問a.py的任何東西,而在b.py中唯一的引用就是調用a.f()。但是這個調用是在函數g()中完成的,并且a.py或者b.py中沒有人調用g(),所以這會兒心情還是美麗的。

但是當我們試圖加載b.py時(之前沒有加載a.py),會發生什么呢:

Python
12345678910>>>importbTraceback(mostrecentcalllast):     File"<stdin>",line1,in<module>     File"b.py",line1,in<module>    importa     File"a.py",line6,in<module>printf()     File"a.py",line4,infreturnb.xAttributeError:'module'objecthasnoattribute'x'

恭喜你,出錯了。這里問題出在加載b.py的過程中,Python試圖加載a.py,并且在a.py中需要調用到f(),而函數f()又要訪問到b.x,但是這個時候b.x卻還沒有被定義。這就產生了AttributeError異常。

解決的方案可以做一點細微的改動。改一下b.py,使得它在g()里面加載a.py:

Python
1234x = 1def g():    import a # 只有當g()被調用的時候才加載    print a.f()

這會兒當我們加載b.py的時候,一切安好:

Python
1234>>>importb>>>b.g()1# 第一次輸出,因為模塊a在最后調用了‘print f()’1# 第二次輸出,這是我們調用g()

常見錯誤8:與Python標準庫模塊命名沖突

Python的一個優秀的地方在于它提供了豐富的庫模塊。但是這樣的結果是,如果你不下意識的避免,很容易你會遇到你自己的模塊的名字與某個隨Python附帶的標準庫的名字沖突的情況(比如,你的代碼中可能有一個叫做email.py的模塊,它就會與標準庫中同名的模塊沖突)。

這會導致一些很粗糙的問題,例如當你想加載某個庫,這個庫需要加載Python標準庫里的某個模塊,結果呢,因為你有一個與標準庫里的模塊同名的模塊,這個包錯誤的將你的模塊加載了進去,而不是加載Python標準庫里的那個模塊。這樣一來就會有麻煩了。

所以在給模塊起名字的時候要小心了,得避免與Python標準庫中的模塊重名。相比起你提交一個“Python改進建議(Python Enhancement Proposal (PEP))”去向上要求改一個標準庫里包的名字,并得到批準來說,你把自己的那個模塊重新改個名字要簡單得多。

常見錯誤9:不能區分Python 2和Python 3

看下面這個文件foo.py:

Python
12345678910111213141516171819import 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里,運行起來沒有問題:

123456$pythonfoo.py1keyerror1$pythonfoo.py2valueerror2

但是如果拿到Python 3上面玩玩:

12345678$ python3 foo.py 1key errorTraceback (most recent call last):  File &quot;foo.py&quot;, line 19, in &lt;module&gt;    bad()  File &quot;foo.py&quot;, line 17, in bad    print(e)UnboundLocalError: local variable &#039;e&#039; referenced before assignment

這是怎么回事?“問題”在于,在Python 3里,在except塊的作用域以外,異常對象(exception object)是不能被訪問的。(原因在于,如果不這樣的話,Python會在內存的堆棧里保持一個引用鏈直到Python的垃圾處理將這些引用從內存中清除掉。更多的技術細節可以參考這里。)

避免這樣的問題可以這樣做:保持在execpt塊作用域以外對異常對象的引用,這樣是可以訪問的。下面是用這個辦法對之前的例子做的改動,這樣在Python 2和Python 3里面都運行都沒有問題。

Python
123456789101112131415161718192021importsys defbar(i):    ifi==1:        raiseKeyError(1)    ifi==2:        raiseValueError(2) defgood():    exception=None    try:        bar(int(sys.argv[1]))    exceptKeyErrorase:        exception=e        print('key error')    exceptValueErrorase:        exception=e        print('value error')    print(exception) good()

在Py3k里面運行:

123456$ python3 foo.py 1key error1$ python3 foo.py 2value error2

耶!

(順帶提一下,我們的“Python招聘指南”里討論了從Python 2移植代碼到Python 3時需要注意的其他重要的不同之處。)

常見錯誤10:錯誤的使用__del__方法

假設有一個文件mod.py中這樣使用:

Python
123456importfoo classBar(object):       ...    def__del__(self):        foo.cleanup(self.myhandle)

然后試圖在another_mod.py里這樣:

Python
12import modmybar = mod.Bar()

那么你會得到一個惡心的AttributeError異常。

為啥呢?這是因為(參考這里),當解釋器關閉時,模塊所有的全局變量會被置為空(None)。結果便如上例所示,當__del__被調用時,名字foo已經被置為空了。

使用atexit.register()可以解決這個問題。如此,當你的程序結束的時候(退出的時候),你的注冊的處理程序會在解釋器關閉之前處理。

這樣理解的話,對上面的mod.py可以做如下的修改:

Python
12345678910importfooimportatexit defcleanup(handle):    foo.cleanup(handle) classBar(object):    def__init__(self):        ...        atexit.register(cleanup,self.myhandle)

這樣的實現方式為在程序正常終止時調用清除功能提供了一種干凈可靠的辦法。顯然,需要foo.cleanup決定怎么處理綁定在self.myhandle上的對象,但你知道怎么做的。

總結

Python 是一門非常強大且靈活的語言,它眾多的機制和范式能顯著的提高生產效率。不過,和任何一款軟件或者語言一樣,對它的理解或認識不足的話,常常是弊大于利的,并會處于一種“一知半解”的狀態。

多熟悉Python的一些關鍵的細微的地方,比如(但不局限于)本文中提到的這些問題,可以幫你更好的使用這門語言的同時幫你避免一些常見的陷阱。

感興趣的話可以讀一讀這篇“Python面試指南(Insider’s Guide to Python Interviewing)”,了解一些能夠區分Python程序員的面試題目。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
www.色综合| 日本久久久久亚洲中字幕| 国产精品久久久久久中文字| 国外成人免费在线播放| 欧美一级视频在线观看| 亚洲欧美国产精品专区久久| 欧美午夜精品久久久久久久| 国产美女精彩久久| 成人亲热视频网站| 日韩在线视频观看| 欧美大片在线看免费观看| 中文字幕在线看视频国产欧美在线看完整| 国产视频精品一区二区三区| 91av视频导航| 色999日韩欧美国产| 国产精品久久久久久久久久久新郎| 精品久久久久久久中文字幕| 国产精品主播视频| 亚洲一区二区三区在线免费观看| 91精品国产色综合| 欧美做受高潮电影o| 日本中文字幕久久看| 成人黄色片在线| 亚洲精品久久久久久久久久久久久| 深夜福利91大全| 久久深夜福利免费观看| 亚洲第一二三四五区| 日韩欧美国产一区二区| 久久久欧美一区二区| 国产精品久久久久久久9999| 日韩av免费在线播放| 亚洲福利在线播放| 欧美午夜精品久久久久久浪潮| 亚洲色图日韩av| 一个人看的www久久| 国产精品人人做人人爽| 国产91精品久久久久久| 国产99视频在线观看| 亚洲精品xxx| 91亚洲午夜在线| 91精品国产色综合久久不卡98口| 日韩三级影视基地| 亚洲国产精品一区二区三区| 亚洲精品免费在线视频| 国产一区二区三区18| 国产成人精品优优av| 国产欧美亚洲视频| 日韩在线视频线视频免费网站| 欧美在线www| 亚洲激情在线视频| 国产91在线播放精品91| 久久99国产精品久久久久久久久| 欧美大全免费观看电视剧大泉洋| 欧美福利视频在线| 国产精品丝袜白浆摸在线| 26uuu亚洲国产精品| 九色成人免费视频| 日韩高清欧美高清| 国产精品久久久av久久久| 亚洲一品av免费观看| 在线视频国产日韩| 成人有码视频在线播放| 日韩激情第一页| 97在线看免费观看视频在线观看| 日韩成人高清在线| 亚洲国产精久久久久久| 国产精品jvid在线观看蜜臀| wwwwwwww亚洲| 中文字幕少妇一区二区三区| 亚洲欧洲偷拍精品| 亚洲v日韩v综合v精品v| 中文字幕精品—区二区| 精品久久久久久久久久ntr影视| 欧美成人自拍视频| 日韩精品在线视频| 国产91精品黑色丝袜高跟鞋| 中文欧美日本在线资源| 热久久免费国产视频| 国产在线不卡精品| 亚洲精品福利在线观看| 亚洲成人精品视频| 亚洲已满18点击进入在线看片| 日韩欧美精品免费在线| 中文字幕在线看视频国产欧美在线看完整| 91精品国产91久久久久久久久| 秋霞av国产精品一区| 2019最新中文字幕| 91麻豆国产语对白在线观看| 亚洲国产精品yw在线观看| 久久国产精品影视| 中文字幕综合一区| 国产精品精品视频| 欧美激情视频一区| 亚洲大尺度美女在线| 亚洲国产成人精品久久久国产成人一区| 92福利视频午夜1000合集在线观看| 久久精品99久久久久久久久| 亚洲成色777777在线观看影院| 欧美日韩性生活视频| 日韩精品高清在线| 国产亚洲aⅴaaaaaa毛片| 欧美激情图片区| 欧美又大粗又爽又黄大片视频| 久久99精品视频一区97| 国产一区二区三区免费视频| 久久久亚洲影院你懂的| 自拍偷拍亚洲欧美| 亚洲一区二区福利| 亚洲欧美激情四射在线日| 久久国内精品一国内精品| 久久成人精品一区二区三区| 欧美日韩免费区域视频在线观看| 2019国产精品自在线拍国产不卡| 日韩综合中文字幕| 久久久久国产精品免费| 亚洲香蕉成视频在线观看| 欧美性猛交丰臀xxxxx网站| 欧美性xxxxxx| 日日狠狠久久偷偷四色综合免费| 久久久免费高清电视剧观看| 精品国产一区二区三区久久久| 国产精品精品视频一区二区三区| 欧美三级免费观看| 81精品国产乱码久久久久久| 亚洲色图综合久久| 亚洲欧美日韩久久久久久| 91精品国产91久久久久福利| 8050国产精品久久久久久| 国产欧美久久一区二区| 日本91av在线播放| 97在线视频免费播放| 亚洲午夜未满十八勿入免费观看全集| 色婷婷成人综合| 精品国产福利视频| 亚洲欧美国产精品专区久久| 奇米一区二区三区四区久久| 狠狠色狠狠色综合日日小说| 久久视频在线直播| 2019中文字幕全在线观看| 欧美伦理91i| 午夜精品久久久久久99热| 国外成人在线视频| 国产在线精品播放| 亚洲人成电影网| 欧美裸身视频免费观看| 欧美老肥婆性猛交视频| 国产亚洲精品久久久久久| 一区二区三区美女xx视频| 韩剧1988在线观看免费完整版| 国产精品成人免费电影| 韩国精品久久久999| 国产视频亚洲视频| 欧美富婆性猛交| 亚洲国产成人一区| 日韩福利视频在线观看| www日韩中文字幕在线看| 色青青草原桃花久久综合| 久久久99久久精品女同性| 欧美精品一区二区免费| 欧美性猛交xxxx乱大交极品| 亚洲精品久久久久久久久久久久| 欧美日韩国产第一页| 日韩免费在线视频| 国产亚洲精品久久久久动|