Python中常見(jiàn)的9大坑,看看你有沒(méi)有遇到!

Python作為一門簡(jiǎn)潔且容易上手的語(yǔ)言,正在受到越來(lái)越多人的喜愛(ài)。但如果你對(duì)其中的一些細(xì)節(jié)不甚了解,就有可能掉入它的“坑”里。本文將介紹學(xué)習(xí)Python過(guò)程中遇到的一些問(wèn)題,接下來(lái)看看Python里一些常見(jiàn)的坑。

01
三元表達(dá)式

image

乍一看,按照根深蒂固的四則運(yùn)算的思維,加號(hào)之前是一部分,加號(hào)之后為另一部分,結(jié)果貌似等于10。為什么打印出來(lái)的結(jié)果跟我們預(yù)想的大相徑庭呢?很顯然,Python解釋器在遇到三元表達(dá)式時(shí),默認(rèn)把if之前的(10+4)作為三元表達(dá)式的前面部分了。

02

嵌套列表的創(chuàng)建

要?jiǎng)?chuàng)建一個(gè)嵌套的列表,我們可能會(huì)選擇這樣的方式:

image

目前看起來(lái)一切正常,我們得到了一個(gè)包含3個(gè)list的嵌套list。接下來(lái)往第一個(gè)list中添加一個(gè)元素:

image

奇怪的事情發(fā)生了,我們只想給第一元素增加元素,結(jié)果三個(gè)list都增加了一個(gè)元素。這是因?yàn)閇[]]*3并不是創(chuàng)建了三個(gè)不同list,而是創(chuàng)建了三個(gè)指向同一個(gè)list的對(duì)象,所以,當(dāng)我們操作第一個(gè)元素時(shí),其他兩個(gè)元素內(nèi)容也會(huì)發(fā)生變化。下面的代碼可以證實(shí)這一點(diǎn):

image

正確的做法如下:

image

03

try...finally + return

看下面這段代碼,可以試想一下print語(yǔ)句打印的順序:

image

是不是很多小伙伴看到return就對(duì)print的順序感到不知所措了,下圖是最終的結(jié)果:

image

我們先來(lái)看一段Python官網(wǎng)對(duì)于finally的解釋:

image

翻譯成中文就是try塊中包含break、continue或者return語(yǔ)句的,在離開(kāi)try塊之前,finally中的語(yǔ)句也會(huì)被執(zhí)行。所以在上面的例子中,try塊return之前,會(huì)執(zhí)行finally中的語(yǔ)句,最后再執(zhí)行try中的return語(yǔ)句返回結(jié)果。看到這里小伙伴們都豁然開(kāi)朗了吧。

04

參數(shù)默認(rèn)值

當(dāng)我們把函數(shù)的參數(shù)默認(rèn)值設(shè)置為列表時(shí),會(huì)發(fā)生什么?

image

出現(xiàn)以上情況的原因是:默認(rèn)值在定義函數(shù)時(shí)計(jì)算(通常在加載模塊時(shí)),因此默認(rèn)值變成了函數(shù)對(duì)象的屬性。具體來(lái)說(shuō),函數(shù)的參數(shù)默認(rèn)值保存在函數(shù)的defaults屬性中,指向了同一個(gè)列表。因此,如果默認(rèn)值是可變對(duì)象,而且修改了它的值,那么后續(xù)的函數(shù)調(diào)用都會(huì)受到影響。正確的做法是設(shè)置該參數(shù)默認(rèn)為None。

05
lambda自由參數(shù)之坑

先來(lái)看這樣一段代碼:

image

結(jié)果并不是0,2,4,6,8,而是8,8,8,8,8。不少人可能會(huì)覺(jué)得匪夷所思,不著急,先試著用dis庫(kù)分析字節(jié)碼。

image
image

沒(méi)有得到想要的結(jié)果,只能看到參數(shù)i和x,參數(shù)i的具體值無(wú)法獲取。這也就是說(shuō)lambda函數(shù)在定義的時(shí)候只知道有一個(gè)i,而他的值并不明確,之后通過(guò)計(jì)算獲取i的值。到這里很容易聯(lián)想到閉包,因?yàn)閕引用了“for i in range(5)”這個(gè)表達(dá)式中的值。先復(fù)習(xí)一下“閉包”的定義:閉包是一種函數(shù),它會(huì)保留定義函數(shù)時(shí)存在的自由變量的綁定,這樣調(diào)用函數(shù)時(shí),雖然定義作用域不可用了,但是仍能使用那些綁定。接下來(lái)驗(yàn)證一下,我們通過(guò)f.code.co_freevars來(lái)獲取自由變量的名稱,通過(guò)f.closure[0].cell_contents得到自由變量的值:

image
image

果不其然,自由變量i最終的值都是4,這也就解釋了最開(kāi)始的結(jié)果。如果還不明白可以看下面這段代碼。

image

Python程序從上到下執(zhí)行,同時(shí)它也是一門動(dòng)態(tài)型的語(yǔ)言,舉個(gè)例子,定義一個(gè)類之后,你可以動(dòng)態(tài)的給它增加方法。同樣,上面這個(gè)例子中,程序執(zhí)行到最后i的值為5,所以lambda表達(dá)式中i為5,最終的結(jié)果為:[10, 10,10, 10, 10, 10]。

要解決上述出現(xiàn)的問(wèn)題,就要把閉包作用域變?yōu)榫植孔饔糜颍?/p>

a = [lambda x, i=i: i*x for i in range(5)]。這行代碼等效于下面這種寫法:

image

“紙上得來(lái)終覺(jué)淺,絕知此事要躬行”。

06

含單個(gè)元素的元組

image

上圖有兩行新建元組的代碼,但只有第二種寫法是正確的。因?yàn)樵谖ㄒ坏脑刂蟛患佣禾?hào),小括號(hào)對(duì)于Python解釋器是無(wú)效的。

07

對(duì)象銷毀順序

創(chuàng)建一個(gè)類OBJ:

image

創(chuàng)建兩個(gè)OBJ示例,使用is判斷是否為同一對(duì)象:

image
image

接下來(lái)同樣創(chuàng)建兩個(gè)對(duì)象,使用id來(lái)判斷。

image
image

調(diào)用id函數(shù), Python 創(chuàng)建一個(gè)OBJ類的實(shí)例,并使用id函數(shù)獲得內(nèi)存地址后,銷毀內(nèi)存丟棄這個(gè)對(duì)象。當(dāng)連續(xù)兩次進(jìn)行此操作, Python會(huì)將相同的內(nèi)存地址分配給第二個(gè)對(duì)象,所以兩個(gè)對(duì)象的id值是相同的。但是is行為卻與之不同,通過(guò)打印順序就可以看到。

08

了解執(zhí)行時(shí)機(jī)

請(qǐng)看下面這個(gè)例子:

image

結(jié)果并不是[1, 3, 5]而是[5],這有些不可思議。原因在于,in子句在聲明時(shí)執(zhí)行, 而條件子句則是在運(yùn)行時(shí)執(zhí)行。所以上圖中的生成器等價(jià)于:

image

09

相同值的不可變對(duì)象

image
image

可以看到,key=1,value=’a’的鍵值對(duì)神奇地消失了。這里不得不說(shuō)一下Python字典是使用哈希表的思想實(shí)現(xiàn)的,Python 調(diào)用內(nèi)部的散列函數(shù),將鍵(Key)作為參數(shù)進(jìn)行轉(zhuǎn)換,得到一個(gè)唯一的地址,也就是哈希值。而Python 的哈希算法對(duì)相同的值計(jì)算得到的結(jié)果是一樣的,這就很好地解釋了上述情況出現(xiàn)的原因。

本文列出了在Python學(xué)習(xí)或者工作中可能會(huì)遇到的一些“坑”,雖然不見(jiàn)得每個(gè)人都能遇到上述問(wèn)題,但是可以作為一個(gè)參考,以后就能避免踩坑了。希望小伙伴在跟Python打交道的過(guò)程中能多注意細(xì)節(jié),甚至去了解一些內(nèi)部實(shí)現(xiàn)的原理,這樣才能更好地掌握Python這門語(yǔ)言。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容