摘要:原文鏈接最近偶爾翻看,遇到有意思的東西就記下來(lái)下面的是在上提出的一個(gè)關(guān)于的也就是增量賦值的一個(gè)問(wèn)題。再看的過(guò)程,前面都一樣,只有這一行這個(gè)直接調(diào)用內(nèi)置函數(shù)完成了對(duì)原列表的修改,其中并沒(méi)有操作,因此可以正常執(zhí)行。
問(wèn)題原文鏈接
最近偶爾翻看Fluent Python,遇到有意思的東西就記下來(lái). 下面的是在PyCon2013上提出的一個(gè)關(guān)于tuple的Augmented Assignment也就是增量賦值的一個(gè)問(wèn)題。 并且基于此問(wèn)題, 又引申出3個(gè)變種問(wèn)題.
首先看第一個(gè)問(wèn)題, 如下面的代碼段:
>>> t = (1,2, [30,40]) >>> t[2] += [50,60]
會(huì)產(chǎn)生什么結(jié)果呢? 給出了四個(gè)選項(xiàng):
t 變成 [1,2, [30,40,50,60]
TypeError is raised with the message "tuple" object does not support item assignment
Neither 1 nor 2
Both 1 and 2
按照之前的理解, tuple里面的元素是不能被修改的,因此會(huì)選2. 如果真是這樣的話(huà),這篇筆記就沒(méi)必要了,F(xiàn)luent Python中也就不會(huì)拿出一節(jié)來(lái)講了。 正確答案是4
>>> t = (1,2,[30,40]) >>> t[2] += [50,60] Traceback (most recent call last): File "", line 1, in TypeError: "tuple" object does not support item assignment >>> t (1, 2, [30, 40, 50, 60])
問(wèn)題來(lái)了,為什么異常都出來(lái)了, t還是變了?
再看第二種情況,稍微變化一下,將+=變?yōu)?b>=:
>>> t = (1,2, [30,40]) >>> t[2] = [50,60]
結(jié)果就成醬紫了:
>>> t = (1,2, [30,40]) >>> t[2] = [50,60] Traceback (most recent call last): File "", line 1, in TypeError: "tuple" object does not support item assignment >>> t (1, 2, [30, 40])
再看第三種情況,只把+=換為extend或者append,:
>>> t = (1, 2, [30,40]) >>> t[2].extend([50,60]) >>> t (1, 2, [30, 40, 50, 60]) >>> t[2].append(70) >>> t (1, 2, [30, 40, 50, 60, 70])
又正常了,沒(méi)拋出異常?
最后第四種情況, 用變量的形式:
>>> a = [30,40] >>> t = (1, 2, a) >>> a+=[50,60] >>> a [30, 40, 50, 60] >>> t (1, 2, [30, 40, 50, 60]) >>> t[2] += [70,80] Traceback (most recent call last): File "", line 1, in TypeError: "tuple" object does not support item assignment >>> t (1, 2, [30, 40, 50, 60, 70, 80])
又是一種情況, 下面就探究一下其中的原因.
原因首先需要重溫+=這個(gè)運(yùn)算符,如a+=b:
對(duì)于可變對(duì)象(mutable object)如list, +=操作的結(jié)果會(huì)直接在a對(duì)應(yīng)的變量進(jìn)行修改,而a對(duì)應(yīng)的地址不變.
對(duì)于不可變對(duì)象(imutable object)如tuple, +=則是等價(jià)于a = a+b 會(huì)產(chǎn)生新的變量,然后綁定到a上而已.
如下代碼段, 可以看出來(lái):
>>> a = [1,2,3] >>> id(a) 53430752 >>> a+=[4,5] >>> a [1, 2, 3, 4, 5] >>> id(a) 53430752 # 地址沒(méi)有變化 >>> b = (1,2,3) >>> id(b) 49134888 >>> b += (4,5) >>> b (1, 2, 3, 4, 5) >>> id(b) 48560912 # 地址變化了
此外還需要注意的是, python中的tuple作為不可變對(duì)象, 也就是我們平時(shí)說(shuō)的元素不能改變, 實(shí)際上從報(bào)錯(cuò)信息TypeError: "tuple" object does not support item assignment來(lái)看, 更準(zhǔn)確的說(shuō)法是指其中的元素不支持賦值操作=(assignment).
先看最簡(jiǎn)單的第二種情況, 它的結(jié)果是符合我們的預(yù)期, 因?yàn)?b>=產(chǎn)生了assign的操作.(在由一個(gè)例子到python的名字空間 中指出了賦值操作=就是創(chuàng)建新的變量), 因此s[2]=[50,60]就會(huì)拋出異常.
再看第三種情況,包含extend/append的, 結(jié)果tuple中的列表值發(fā)生了變化,但是沒(méi)有異常拋出. 這個(gè)其實(shí)也相對(duì)容易理解. 因?yàn)槲覀冎?b>tuple中存儲(chǔ)的其實(shí)是元素所對(duì)應(yīng)的地址(id), 因此如果沒(méi)有賦值操作且tuple中的元素的id不變,即可,而list.extend/append只是修改了列表的元素,而列表本身id并沒(méi)有變化,看看下面的例子:
>>> a=(1,2,[30,40]) >>> id(a[2]) 140628739513736 >>> a[2].extend([50,60]) >>> a (1, 2, [30, 40, 50, 60]) >>> id(a[2]) 140628739513736
目前解決了第二個(gè)和第三個(gè)問(wèn)題, 先梳理一下, 其實(shí)就是兩點(diǎn):
tuple內(nèi)部的元素不支持賦值操作
在第一條的基礎(chǔ)上, 如果元素的id沒(méi)有變化, 元素其實(shí)是可以改變的.
現(xiàn)在再來(lái)看最初的第一個(gè)問(wèn)題: t[2] += [50,60] 按照上面的結(jié)論, 不應(yīng)該拋異常啊,因?yàn)樵谖覀兛磥?lái)+= 對(duì)于可變對(duì)象t[2]來(lái)說(shuō), 屬于in-place操作,也就是直接修改自身的內(nèi)容, id并不變, 確認(rèn)下id并沒(méi)有變化:
>>> a=(1,2,[30,40]) >>> id(a[2]) 140628739587392 >>> a[2]+=[50,60] Traceback (most recent call last): File "", line 1, in TypeError: "tuple" object does not support item assignment >>> a (1, 2, [30, 40, 50, 60]) >>> id(a[2]) # ID 并沒(méi)有發(fā)生改變 140628739587392
跟第三個(gè)問(wèn)題僅僅從t[2].extend改成了t[2]+=, 就拋出異常了,所以問(wèn)題應(yīng)該是出在+=上了.
下面用dis模塊看看它倆執(zhí)行的步驟:
對(duì)下面的代碼塊執(zhí)行dis:
t = (1,2, [30,40]) t[2] += [50,60] t[2].extend([70, 80])
執(zhí)行python -m dis test.py,結(jié)果如下,下面只保留第2,3行代碼的執(zhí)行過(guò)程,以及關(guān)鍵步驟的注釋如下:
2 21 LOAD_NAME 0 (t) 24 LOAD_CONST 1 (2) 27 DUP_TOPX 2 30 BINARY_SUBSCR 31 LOAD_CONST 4 (50) 34 LOAD_CONST 5 (60) 37 BUILD_LIST 2 40 INPLACE_ADD 41 ROT_THREE 42 STORE_SUBSCR 3 43 LOAD_NAME 0 (t) 46 LOAD_CONST 1 (2) 49 BINARY_SUBSCR 50 LOAD_ATTR 1 (extend) 53 LOAD_CONST 6 (70) 56 LOAD_CONST 7 (80) 59 BUILD_LIST 2 62 CALL_FUNCTION 1 65 POP_TOP 66 LOAD_CONST 8 (None) 69 RETURN_VALUE
解釋一下關(guān)鍵的語(yǔ)句:
30 BINARY_SUBSCR: 表示將t[2]的值放在TOS(Top of Stack),這里是指[30, 40]這個(gè)列表
40 INPLACE_ADD: 表示TOS += [50,60] 執(zhí)行這一步是可以成功的,修改了TOS的列表為[30,40,50,60]
42 STORE_SUBSCR: 表示s[2] = TOS 問(wèn)題就出在這里了,這里產(chǎn)生了一個(gè)賦值操作,因此會(huì)拋異常!但是上述對(duì)列表的修改已經(jīng)完成, 這也就解釋了開(kāi)篇的第一個(gè)問(wèn)題。
再看extend的過(guò)程,前面都一樣,只有這一行:
62 CALL_FUNCTION: 這個(gè)直接調(diào)用內(nèi)置extend函數(shù)完成了對(duì)原列表的修改,其中并沒(méi)有assign操作,因此可以正常執(zhí)行。
現(xiàn)在逐漸清晰了, 換句話(huà)說(shuō),+=并不是原子操作,相當(dāng)于下面的兩步:
t[2].extend([50,60]) t[2] = t[2]
第一步可以正確執(zhí)行,但是第二步有了=,肯定會(huì)拋異常的。 同樣這也可以解釋在使用+=的時(shí)候,為何t[2]的id明明沒(méi)有變化,但是仍然拋出異常了。
現(xiàn)在用一句話(huà)總結(jié)下:
tuple中元素不支持assign操作,但是對(duì)于那些是可變對(duì)象的元素如列表,字典等,在沒(méi)有assign操作的基礎(chǔ)上,比如一些in-place操作,是可以修改內(nèi)容的
可以用第四個(gè)問(wèn)題來(lái)簡(jiǎn)單驗(yàn)證一下,使用一個(gè)指向[30,40]的名稱(chēng)a來(lái)作為元素的值,然后對(duì)a做in-place的修改,其中并沒(méi)有涉及到對(duì)tuple的assign操作,那肯定是正常執(zhí)行的。
總結(jié)這個(gè)問(wèn)題其實(shí)以前也就遇到過(guò),但是沒(méi)想過(guò)具體的原理,后來(lái)翻書(shū)的時(shí)候又看到了, 于是花了點(diǎn)時(shí)間把這一個(gè)系列查了部分資料以及結(jié)合自己的理解都整理了出來(lái), 算是飯后茶點(diǎn)吧, 不嚴(yán)謹(jǐn)?shù)牡胤綗┱?qǐng)指出.
部分參考如下:
python bugs
python faq
stackoverflow
Fluent Python
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/40753.html
摘要:同樣的用上節(jié)講的函數(shù)獲取元素的個(gè)數(shù)記住這是獲取的是列表個(gè)數(shù)個(gè)數(shù)個(gè)數(shù)重要的事說(shuō)三遍。用索引訪問(wèn)每個(gè)元素的位置,索引是從開(kāi)始的開(kāi)始的開(kāi)始的索引也是有容忍限度的超過(guò)了就會(huì)原地爆炸報(bào)錯(cuò)如果列表中元素個(gè)數(shù)賊多,想獲取后面的元素就要實(shí)行曲線(xiàn)救國(guó)了。 list和tuple是Python內(nèi)置的有序集合,一個(gè)是可變的,一個(gè)是不可變滴;這都不是事,主要是理解一下指向不變。 看圖說(shuō)話(huà): showImg(ht...
摘要:邏輯運(yùn)算符假設(shè),運(yùn)算符描述實(shí)例布爾與如果為,返回,否則它返回的計(jì)算值。布爾或如果是,它返回,否則它返回的計(jì)算值。以為例,說(shuō)明語(yǔ)句。逗號(hào)表示打印在同一行本來(lái),在語(yǔ)句中,字符串后面會(huì)接一個(gè)符號(hào)。 運(yùn)算符 算術(shù)運(yùn)算符 前面已經(jīng)講過(guò)了四則運(yùn)算,其中涉及到一些運(yùn)算符:加減乘除,對(duì)應(yīng)的符號(hào)分別是:+ - * /,此外,還有求余數(shù)的:%。這些都是算術(shù)運(yùn)算符。其實(shí),算術(shù)運(yùn)算符不止這些。根據(jù)中學(xué)數(shù)...
摘要:零預(yù)備知識(shí)字符編碼計(jì)算機(jī)只能處理數(shù)字,所以為文本需要轉(zhuǎn)化為數(shù)字才能被計(jì)算機(jī)處理,計(jì)算機(jī)里八個(gè)比特作為一個(gè)字節(jié),這是數(shù)據(jù)的存儲(chǔ)基礎(chǔ)單位。 零、預(yù)備知識(shí) 0.1 字符編碼計(jì)算機(jī)只能處理數(shù)字,所以為文本需要轉(zhuǎn)化為數(shù)字才能被計(jì)算機(jī)處理,計(jì)算機(jī)里八個(gè)比特(bit)作為一個(gè)字節(jié)(byte),這是數(shù)據(jù)的存儲(chǔ)基礎(chǔ)單位。計(jì)算機(jī)為了處理文本,有以下三種編碼方式: ASCII碼:只有大小寫(xiě)英文字母,數(shù)字...
摘要:剛開(kāi)始學(xué)習(xí)一門(mén)編程語(yǔ)言,除了了解運(yùn)行環(huán)境與語(yǔ)言類(lèi)型之外,最基本還是從該語(yǔ)言的基本數(shù)據(jù)類(lèi)型開(kāi)始學(xué)起。六大常用數(shù)據(jù)類(lèi)型整數(shù)浮點(diǎn)數(shù)字符串列表元組字典講解這些先說(shuō)一下中的變量與變量名。支持對(duì)整數(shù)和浮點(diǎn)數(shù)直接進(jìn)行四則混合運(yùn)算。 剛開(kāi)始學(xué)習(xí)一門(mén)編程語(yǔ)言,除了了解運(yùn)行環(huán)境與語(yǔ)言類(lèi)型之外,最基本還是從該語(yǔ)言的基本數(shù)據(jù)類(lèi)型開(kāi)始學(xué)起。 Python六大常用數(shù)據(jù)類(lèi)型: int 整數(shù) floa...
閱讀 2228·2021-11-22 13:54
閱讀 3385·2019-08-29 12:25
閱讀 3449·2019-08-28 18:29
閱讀 3595·2019-08-26 13:40
閱讀 3284·2019-08-26 13:32
閱讀 971·2019-08-26 11:44
閱讀 2241·2019-08-23 17:04
閱讀 2979·2019-08-23 17:02