使用Python的坑有哪些-創(chuàng)新互聯(lián)

這篇文章主要講解了“使用Python的坑有哪些”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“使用Python的坑有哪些”吧!

創(chuàng)新互聯(lián)建站專注于雙橋網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠(chéng)為您提供雙橋營(yíng)銷型網(wǎng)站建設(shè),雙橋網(wǎng)站制作、雙橋網(wǎng)頁(yè)設(shè)計(jì)、雙橋網(wǎng)站官網(wǎng)定制、微信小程序服務(wù),打造雙橋網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供雙橋網(wǎng)站排名全網(wǎng)營(yíng)銷落地服務(wù)。

坑01 - 整數(shù)比較的坑

在 Python 中一切都是對(duì)象,整數(shù)也是對(duì)象,在比較兩個(gè)整數(shù)時(shí)有兩個(gè)運(yùn)算符 == 和 is ,它們的區(qū)別是:

  • is 比較的是兩個(gè)整數(shù)對(duì)象的id值是否相等,也就是比較兩個(gè)引用是否代表了內(nèi)存中同一個(gè)地址。

  • == 比較的是兩個(gè)整數(shù)對(duì)象的內(nèi)容是否相等,使用==時(shí)其實(shí)是調(diào)用了對(duì)象的__eq__()方法。

知道了is和==的區(qū)別之后,我們可以來(lái)看看下面的代碼,了解Python中整數(shù)比較有哪些坑:

def main():
	x = y = -1
	while True:
		x += 1
		y += 1
		if x is y:
			print('%d is %d' % (x, y))
		else:
			print('Attention! %d is not %d' % (x, y))
			break
			
	x = y = 0
	while True:
		x -= 1
		y -= 1
		if x is y:
			print('%d is %d' % (x, y))
		else:
			print('Attention! %d is not %d' % (x, y))
			break
if __name__ == '__main__':
	main()

上面代碼的部分運(yùn)行結(jié)果如下圖所示,出現(xiàn)這個(gè)結(jié)果的原因是Python出于對(duì)性能的考慮所做的一項(xiàng)優(yōu)化。對(duì)于整數(shù)對(duì)象,Python把一些頻繁使用的整數(shù)對(duì)象緩存起來(lái),保存到一個(gè)叫small_ints的鏈表中,在Python的整個(gè)生命周期內(nèi),任何需要引用這些整數(shù)對(duì)象的地方,都不再重新創(chuàng)建新的對(duì)象,而是直接引用緩存中的對(duì)象。Python把頻繁使用的整數(shù)對(duì)象的值定在[-5, 256]這個(gè)區(qū)間,如果需要這個(gè)范圍的整數(shù),就直接從 small_ints 中獲取引用而不是臨時(shí)創(chuàng)建新的對(duì)象。因?yàn)榇笥?56或小于-5的整數(shù)不在該范圍之內(nèi),所以就算兩個(gè)整數(shù)的值是一樣,但它們是不同的對(duì)象。

當(dāng)然僅僅如此這個(gè)坑就不值一提了,如果你理解了上面的規(guī)則,我們就再看看下面的代碼。

import dis
a = 257
def main():
	b = 257 # 第6行
	c = 257 # 第7行
	print(b is c) # True
	print(a is b) # False
	print(a is c) # False
if __name__ == "__main__":
	main()

程序的執(zhí)行結(jié)果已經(jīng)用注釋寫在代碼上了。夠坑吧!看上去a、b和c的值都是一樣的,但是 is運(yùn)算的結(jié)果卻不一樣。為什么會(huì)出現(xiàn)這樣的結(jié)果,首先我們來(lái)說(shuō)說(shuō)Python程序中的代碼塊。

所謂代碼塊是程序的一個(gè)最小的基本執(zhí)行單位,一個(gè)模塊文件、一個(gè)函數(shù)體、一個(gè)類、交互式命令中的單行代碼都叫做一個(gè)代碼塊。上面的代碼由兩個(gè)代碼塊構(gòu)成,a = 257是一個(gè)代碼塊,main函數(shù)是另外一個(gè)代碼塊。

Python內(nèi)部為了進(jìn)一步提高性能,凡是在一個(gè)代碼塊中創(chuàng)建的整數(shù)對(duì)象,如果值不在small_ints 緩存范圍之內(nèi),但在同一個(gè)代碼塊中已經(jīng)存在一個(gè)值與其相同的整數(shù)對(duì)象了,那么就直接引用該對(duì)象,否則創(chuàng)建一個(gè)新的對(duì)象出來(lái),這條規(guī)則對(duì)不在small_ints范圍的負(fù)數(shù)并不適用,對(duì)負(fù)數(shù)值浮點(diǎn)數(shù)也不適用,但對(duì)非負(fù)浮點(diǎn)數(shù)和字符串都是適用的,這一點(diǎn)讀者可以自行證明。所以 b is c返回了True,而a和b不在同一個(gè)代碼塊中,雖然值都是257,但卻是兩個(gè)不同的對(duì)象,is運(yùn)算的結(jié)果自然是False了。

為了驗(yàn)證剛剛的結(jié)論,我們可以借用dis模塊(聽(tīng)名字就知道是進(jìn)行反匯編的模塊)從字節(jié)碼的角度來(lái)看看這段代碼。如果不理解什么是字節(jié)碼,可以先看看《談?wù)?Python 程序的運(yùn)行原理》這篇文章??梢韵扔胕mport dis導(dǎo)入dis模塊并按照如下所示的方式修改代碼。

if __name__ == "__main__":
	main()
	dis.dis(main)

代碼的執(zhí)行結(jié)果如下圖所示。

可以看出代碼第6行和第7行,也就是main函數(shù)中的257是從同一個(gè)位置加載的,因此是同一個(gè)對(duì)象;而代碼第9行的a明顯是從不同的地方加載的,因此引用的是不同的對(duì)象。

如果還想對(duì)這個(gè)問(wèn)題進(jìn)行進(jìn)一步深挖,推薦大家閱讀《Python整數(shù)對(duì)象實(shí)現(xiàn)原理》這篇文章。

坑02 - 嵌套列表的坑

Python中有一種內(nèi)置的數(shù)據(jù)類型叫列表,它是一種容器,可以用來(lái)承載其他的對(duì)象(準(zhǔn)確的說(shuō)是其他對(duì)象的引用),列表中的對(duì)象可以稱為列表的元素,很明顯我們可以把列表作為列表中的元素,這就是所謂的嵌套列表。嵌套列表可以模擬出現(xiàn)實(shí)中的表格、矩陣、2D游戲的地圖(如植物大戰(zhàn)僵尸的花園)、棋盤(如國(guó)際象棋、黑白棋)等。但是在使用嵌套的列表時(shí)要小心,否則很可能遭遇非常尷尬的情況,下面是一個(gè)小例子。

def main():
	names = ['關(guān)羽', '張飛', '趙云', '馬超', '黃忠']
	subjs = ['語(yǔ)文', '數(shù)學(xué)', '英語(yǔ)']
	scores = [[0] * 3] * 5
	for row, name in enumerate(names):
		print('請(qǐng)輸入%s的成績(jī)' % name)
		for col, subj in enumerate(subjs):
			scores[row][col] = float(input(subj + ': '))
	print(scores)
if __name__ == '__main__':
	main()

我們希望錄入5個(gè)學(xué)生3門課程的成績(jī),于是定義了一個(gè)有5個(gè)元素的列表,而列表中的每個(gè)元素又是一個(gè)由3個(gè)元素構(gòu)成的列表,這樣一個(gè)列表的列表剛好跟一個(gè)表格是一致的,相當(dāng)于有5行3列,接下來(lái)我們通過(guò)嵌套的for-in循環(huán)輸入每個(gè)學(xué)生3門課程的成績(jī)。程序執(zhí)行完成后我們發(fā)現(xiàn),每個(gè)學(xué)生3門課程的成績(jī)是一模一樣的,而且就是最后錄入的那個(gè)學(xué)生的成績(jī)。

要想把這個(gè)坑填平,我們首先要區(qū)分對(duì)象和對(duì)象的引用這兩個(gè)概念,而要區(qū)分這兩個(gè)概念,還得先說(shuō)說(shuō)內(nèi)存中的棧和堆。我們經(jīng)常會(huì)聽(tīng)人說(shuō)起“堆?!边@個(gè)詞,但實(shí)際上“堆”和“?!笔莾蓚€(gè)不同的概念。眾所周知,一個(gè)程序運(yùn)行時(shí)需要占用一些內(nèi)存空間來(lái)存儲(chǔ)數(shù)據(jù)和代碼,那么這些內(nèi)存從邏輯上又可以做進(jìn)一步的劃分。對(duì)底層語(yǔ)言(如C語(yǔ)言)有所了解的程序員大都知道,程序中可以使用的內(nèi)存從邏輯上可以為五個(gè)部分,按照地址從高到低依次是:棧(stack)、堆(heap)、數(shù)據(jù)段(data segment)、只讀數(shù)據(jù)段(static area)和代碼段(code segment)。其中,棧用來(lái)存儲(chǔ)局部、臨時(shí)變量,以及函數(shù)調(diào)用時(shí)保存現(xiàn)場(chǎng)和恢復(fù)現(xiàn)場(chǎng)需要用到的數(shù)據(jù),這部分內(nèi)存在代碼塊開(kāi)始執(zhí)行時(shí)自動(dòng)分配,代碼塊執(zhí)行結(jié)束時(shí)自動(dòng)釋放,通常由編譯器自動(dòng)管理;堆的大小不固定,可以動(dòng)態(tài)的分配和回收,因此如果程序中有大量的數(shù)據(jù)需要處理,這些數(shù)據(jù)通常都放在堆上,如果堆空間沒(méi)有正確的被釋放會(huì)引發(fā)內(nèi)存泄露的問(wèn)題,而像Python、Java等編程語(yǔ)言都使用了垃圾回收機(jī)制來(lái)實(shí)現(xiàn)自動(dòng)化的內(nèi)存管理(自動(dòng)回收不再使用的堆空間)。所以下面的代碼中,變量a并不是真正的對(duì)象,它是對(duì)象的引用,相當(dāng)于記錄了對(duì)象在堆空間的地址,通過(guò)這個(gè)地址我們可以訪問(wèn)到對(duì)應(yīng)的對(duì)象;同理,變量b是列表容器的引用,它引用了堆空間上的列表容器,而列表容器中并沒(méi)有保存真正的對(duì)象,它保存的也僅僅是對(duì)象的引用。

a = object()
b = ['apple', 'pitaya', 'grape']

知道了這一點(diǎn),我們可以回過(guò)頭看看剛才的程序,我們對(duì)列表進(jìn)行[[0] * 3] * 5操作時(shí),僅僅是將[0, 0, 0]這個(gè)列表的地址進(jìn)行了復(fù)制,并沒(méi)有創(chuàng)建新的列表對(duì)象,所以容器中雖然有5個(gè)元素,但是這5個(gè)元素引用了同一個(gè)列表對(duì)象,這一點(diǎn)可以通過(guò)id函數(shù)檢查scores[0]和scores[1]的地址得到證實(shí)。所以正確的代碼應(yīng)該按照如下的方式進(jìn)行修改。

def main():
	names = ['關(guān)羽', '張飛', '趙云', '馬超', '黃忠']
	subjs = ['語(yǔ)文', '數(shù)學(xué)', '英語(yǔ)']
	scores = [[]] * 5
	for row, name in enumerate(names):
		print('請(qǐng)輸入%s的成績(jī)' % name)
		scores[row] = [0] * 3
		for col, subj in enumerate(subjs):
			scores[row][col] = float(input(subj + ': '))
	print(scores)
if __name__ == '__main__':
	main()

或者

def main():
	names = ['關(guān)羽', '張飛', '趙云', '馬超', '黃忠']
	subjs = ['語(yǔ)文', '數(shù)學(xué)', '英語(yǔ)']
	scores = [[0] * 3 for _ in range(5)]
	for row, name in enumerate(names):
		print('請(qǐng)輸入%s的成績(jī)' % name)
		scores[row] = [0] * 3
		for col, subj in enumerate(subjs):
			scores[row][col] = float(input(subj + ': '))
	print(scores)
if __name__ == '__main__':
	main()

如果對(duì)內(nèi)存的使用不是很理解,可以看看PythonTutor網(wǎng)站上提供的代碼可視化執(zhí)行功能,通過(guò)可視化執(zhí)行,我們可以看到內(nèi)存是如何分配的,從而避免在使用嵌套列表或者復(fù)制對(duì)象時(shí)可能遇到的坑。

坑03 - 訪問(wèn)修飾符的坑

用Python做過(guò)面向?qū)ο缶幊痰娜硕贾?,Python的類提供了兩種訪問(wèn)控制權(quán)限,一種是公開(kāi),一種是私有(在屬性或方法前加上雙下劃線)。而用慣了Java或C#這類編程語(yǔ)言的人都知道,類中的屬性(數(shù)據(jù)抽象)通常都是私有的,其目的是為了將數(shù)據(jù)保護(hù)起來(lái);而類中的方法(行為抽象)通常都是公開(kāi)的,因?yàn)榉椒ㄊ菍?duì)象向外界提供的服務(wù)。但是Python并沒(méi)有從語(yǔ)法層面確保私有成員的私密性,因?yàn)樗皇菍?duì)類中所謂的私有成員進(jìn)行了命名的變換,如果知道命名的規(guī)則照樣可以直接訪問(wèn)私有成員,請(qǐng)看下面的代碼。

class Student(object):
 def __init__(self, name, age):
 self.__name = name
 self.__age = age
 def __str__(self):
 return self.__name + ': ' + str(self.__age)
def main():
 stu = Student('駱昊', 38)
 # 'Student' object has no attribute '__name'
 # print(stu.__name)
 # 用下面的方式照樣可以訪問(wèn)類中的私有成員
 print(stu._Student__name)
 print(stu._Student__age)
if __name__ == '__main__':
 main()

Python為什么要做出這樣的設(shè)定呢?用一句廣為流傳的格言來(lái)解釋這個(gè)問(wèn)題:“We are all consenting adults here”(我們都是成年人)。這句話表達(dá)了很多Python程序員的一個(gè)共同觀點(diǎn),那就是開(kāi)放比封閉要好,我們應(yīng)該自己對(duì)自己的行為負(fù)責(zé)而不是從語(yǔ)言層面來(lái)限制對(duì)數(shù)據(jù)或方法的訪問(wèn)。

所以在Python中我們實(shí)在沒(méi)有必要將類中的屬性或方法用雙下劃線開(kāi)頭的命名處理成私有的成員,因?yàn)檫@并沒(méi)有任何實(shí)際的意義。如果想對(duì)屬性或方法進(jìn)行保護(hù),我們建議用單下劃線開(kāi)頭的受保護(hù)成員,雖然它也不能真正保護(hù)這些屬性或方法,但是它相當(dāng)于給調(diào)用者一個(gè)暗示,讓調(diào)用者知道這是不應(yīng)該直接訪問(wèn)的屬性或方法,而且這樣做并不影響子類去繼承這些東西。

需要提醒大家注意的是,Python類中的那些魔法方法,如__str__、__repr__等,這些方法并不是私有成員哦,雖然它們以雙下劃線開(kāi)頭,但是他們也是以雙下劃線結(jié)尾的,這種命名并不是私有成員的命名,這一點(diǎn)對(duì)初學(xué)者來(lái)說(shuō)是比較坑的。

感謝各位的閱讀,以上就是“使用Python的坑有哪些”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)使用Python的坑有哪些這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

網(wǎng)頁(yè)題目:使用Python的坑有哪些-創(chuàng)新互聯(lián)
轉(zhuǎn)載源于:http://bm7419.com/article8/dsdpip.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供商城網(wǎng)站、虛擬主機(jī)網(wǎng)頁(yè)設(shè)計(jì)公司、服務(wù)器托管、響應(yīng)式網(wǎng)站搜索引擎優(yōu)化

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

成都做網(wǎng)站