Python的賦值、切片、淺拷貝與深拷貝的區(qū)別

原文地址:

前言

Python作為一門(mén)高級(jí)語(yǔ)言,與C/C++還是有很大的不同。關(guān)于賦值、切片、淺拷貝深拷貝這一塊,其實(shí)很多人對(duì)其不是很了解的,這就很容易在某些代碼中出現(xiàn)意想不到的結(jié)果,同時(shí)也會(huì)很難找到原因。本文將講述這幾類(lèi)情況的區(qū)別以及使用,盡可能通俗易懂,不會(huì)涉及到底層的實(shí)現(xiàn)原理。

本文所有代碼的執(zhí)行環(huán)境如下:

  • 操作系統(tǒng):Window10

  • Python版本:Python 3.7.0

  • 執(zhí)行方式:CMD窗口+Python解釋器的命令交互模式

賦值、切片、拷貝

本文使用is運(yùn)算符來(lái)判斷對(duì)象間的唯一身份標(biāo)識(shí),也就是id是否相同,is也叫同一性運(yùn)算符

賦值

賦值就是我們通過(guò)=把一個(gè)變量的值賦給另一變量,相當(dāng)于引用,這里的賦值又可以分為幾類(lèi)

賦值:不可變對(duì)象的賦值(在緩存范圍內(nèi))

為了增加程序的運(yùn)行效率,Python3的解析器中實(shí)現(xiàn)了整型數(shù)字和字符串緩存的機(jī)制,

  • 整型數(shù)字的緩存范圍為[-5, 256],即變量值相等且在[-5, 20]范圍內(nèi)的所有變量都是同一個(gè)對(duì)象(這個(gè)是有爭(zhēng)議的,有文章說(shuō)是[-5, 無(wú)窮大],但我實(shí)測(cè)是[-5, 256])
  • 字符串默認(rèn)緩存長(zhǎng)度4096,即變量值相等且長(zhǎng)度在4096以內(nèi)的所有字符串變量是同一個(gè)對(duì)象,(這個(gè)是有爭(zhēng)議的,很多文章說(shuō)是緩存20位,但我實(shí)測(cè)是長(zhǎng)度4096)
# 字符串賦值
str_a = str_b = 'hello' # 相當(dāng)于 str_a = 'hello' 和 str_b = str_a 這兩條語(yǔ)句
str_c = 'hello'
print(str_b is str_a) # 輸出: True
print(str_c is str_a) # 輸出:True,這里輸出True就是因?yàn)榫彺鏅C(jī)制,str_c和str_a的值相等,都是'hello',且長(zhǎng)度在20以內(nèi)

# 整型賦值
int_a = int_b = 100 # 相當(dāng)于 int_a = 100 和 int_b = int_a 這兩條語(yǔ)句
int_c = 10 * 10
print(int_b is int_a) # 輸出True
print(int_c is int_a) # 輸出True,這里輸出True就是緩存機(jī)制,因?yàn)閕nt_c和int_a的值相等,都為100,且在[-5, 256]范圍內(nèi)

賦值:不可變對(duì)象的賦值(不在緩存范圍內(nèi))

# 字符串賦值
str_a = str_b = 'a' * 4097 # 相當(dāng)于 str_a = 'a' * 4097 和 str_b = str_a 這兩條語(yǔ)句
str_c = 'a' * 4097
print(str_b is str_a) # 輸出: True
print(str_c is str_a) # 輸出:False,這里輸出False是因?yàn)殡m然str_c和str_a的值相等,但長(zhǎng)度在為4097,超過(guò)了緩存最大長(zhǎng)度4096

# 整型賦值
int_a = int_b = 1000 # 相當(dāng)于 int_a = 100 和 int_b = int_a 這兩條語(yǔ)句
int_c = 10 * 10 * 10
print(int_b is int_a) # 輸出True
print(int_c is int_a) # 輸出False,這里輸出False是因?yàn)殡m然int_c和int_a的值相等,都為1000,但超出了緩存]范圍[-5, 256]

賦值:可變對(duì)象的賦值

這種情況相當(dāng)于完全引用,"比淺拷貝還要淺拷貝",這里舉個(gè)例,假定list_a為列表,把list_a賦值給list_b,只要不是重新賦值list_a或list_b(list_a=xxx或list_b=yyy)操作,無(wú)論是通過(guò)list_a還是通過(guò)list_b來(lái)操作列表(增、刪、改...),另一個(gè)對(duì)象也會(huì)隨之改變(即list_a和list_b在沒(méi)有重新執(zhí)行賦值操作時(shí),將一直是同一個(gè)對(duì)象

list_a = list_b = [1, 2, 3] # 相當(dāng)于 list_a = [1, 2, 3] 和 list_b = list_a 這兩條語(yǔ)句
list_c = [1, 2, 3]
print(list_b is list_a) # 輸出True,修改list_a會(huì)影響list_b,反之亦然
print(list_c is list_a) # 輸出False,所以修改list_a不會(huì)影響到list_c,反之亦然
# 說(shuō)明,這里所說(shuō)的“比淺拷貝還淺拷貝”是針對(duì)淺拷貝而說(shuō)的,也可以說(shuō)完全沒(méi)拷貝,只是引用
# 淺拷貝中對(duì)本身的修改不會(huì)影響另一個(gè)(對(duì)可變子元素本身的操作才會(huì)影響),而賦值無(wú)論哪種情況的修改都會(huì)影響另一個(gè),(這里的賦值和淺拷貝是針對(duì)可變對(duì)象來(lái)說(shuō)的)

切片

切片就是從某個(gè)對(duì)象中抽取部分的操作,切片操作得到的對(duì)象和原對(duì)象是不同的對(duì)象,但其子元素有可能是同一對(duì)象,這里分為幾種情況說(shuō)明,切片相當(dāng)于淺拷貝

切片:對(duì)“是不可變對(duì)象的子元素“的修改或增刪操作不會(huì)影響另一對(duì)象

list_a = [1, 2, 3, 4]
list_b = list_a[:] # 完全切片
print(list_b is list_a) # 輸出False,list_a和list_b是不同的對(duì)象
list_a.append(5) # 對(duì)對(duì)象list_a進(jìn)行追加元素操作
# list_a[0] = 11 # 對(duì)對(duì)象list_a進(jìn)行修改子元素操作
# del list_a[-1] # 對(duì)對(duì)象list_a進(jìn)行刪除子元素操作
print(list_a) # 輸出[1, 2, 3, 4, 5]
print(list_b) # 輸出[1, 2, 3, 4]
print(list_a is list_b) # 輸出False,不是同一個(gè)對(duì)象

切片:對(duì)“是可變對(duì)象的子元素“的操作會(huì)影響另一對(duì)象

list_a = [1, 2, [3], 4] # 和上面不同在于list_a[2]是一個(gè)可變對(duì)象
list_b = list_a[:] # 完全切片
print(list_b is list_a) # 輸出False,list_a和list_b是不同的對(duì)象
list_a[2].append(44) # 對(duì)可變子元素進(jìn)行追加子元素操作,注意是對(duì)子元素本身進(jìn)行追加操作
# list_a[2][0] = 33 # 對(duì)可變子元素進(jìn)行修改子元素操作,注意是對(duì)子元素本身的子元素進(jìn)行修改操作,而不是修改list_a的子元素
# del list_a[2][-1] # 對(duì)可變子元素進(jìn)行刪除子元素操作,注意是對(duì)子元素本身的子元素進(jìn)行刪除操作,而不是刪除list_a的子元素
print(list_) # 輸出[1, 2, [3, 44], 4]
print(list_b) # 輸出[1, 2, [3, 44], 4]
print(list_b is list_a) # 輸出False,list_a和list_b是不同的對(duì)象
print(list_a[2] is list_b[2]) # 輸出True,在切片操作中,可變子元素是相當(dāng)于賦值操作的,即list_a[2]和list_b[2]是同一個(gè)對(duì)象


# 說(shuō)明:這種情況是不區(qū)分完全切片和不完全切片的,只要切片得到的子元素是可變對(duì)象的,都滿足這種情況,以下代碼就是不完全切片的例子,和完全切片的情況一樣的
list_c = list_a[:3] # 不完全切片,但切片得到的list_c[2]是一個(gè)可變對(duì)象
print(list_c is list_a) # 輸出False,list_a和list_c是不同的對(duì)象
list_a[2][0] = 33 # 修改可變子元素的子元素,注意是修改子元素本身的子元素,而不是修改list_a的子元素
print(list_a) # 輸出[1, 2, [33, 44], 4]
print(list_c) # 輸出[1, 2, [33, 44], 4]
print(list_c is list_a) # 輸出False,list_a和list_c是不同的對(duì)象
print(list_a[2] is list_c[2]) # 輸出True,在切片操作中,可變子元素是相當(dāng)于賦值操作的,即list_a[2]和list_c[2]是同一個(gè)對(duì)象

拷貝

相對(duì)于上面的賦值和切片,這里所說(shuō)的拷貝的是通過(guò)copy模塊進(jìn)行拷貝操作

淺拷貝

淺拷貝使用copy.copy(source)方法實(shí)現(xiàn)(某些對(duì)象本身會(huì)提供copy方法,如list.copy),拷貝出來(lái)的對(duì)象和原對(duì)象有可能是同一對(duì)象,如果拷貝的對(duì)象是可變對(duì)象,其子元素有可能是同一對(duì)象

一、對(duì)不可變對(duì)象進(jìn)行淺拷貝,相當(dāng)于深拷貝,類(lèi)似于賦值操作,請(qǐng)參考上面的賦值說(shuō)明,與賦值不同的是,這里拷貝得到的的對(duì)象和原對(duì)象是同一對(duì)象
import copy
a = 'hello'
b = copy.copy(a)
print(b is a) # 輸出True,和賦值一樣

c = 'a' * 4097
d = copy.copy(c)
print(d is c) # 輸出True,和賦值不一樣,已經(jīng)超過(guò)緩存范圍,但還是一樣,這種情況可以類(lèi)比于不設(shè)緩存范圍(肯定緩存)的賦值操作
二、對(duì)可變對(duì)象進(jìn)行淺拷貝,相當(dāng)于完全切片,得到的對(duì)象和原對(duì)象是不同的對(duì)象,但其子元素有可能是同一對(duì)象
import copy
a = [1, [2, [3, [4]]]]
b = copy.copy(a)
print(b is a) # 輸出False,淺拷貝可變對(duì)象得到的對(duì)象和原對(duì)象是不同的對(duì)象
print(b[0] is a[0]) # True,淺拷貝可變對(duì)象得到的對(duì)象的不可變子元素是同一對(duì)象,這和深拷貝是一樣的
print(b[1] is a[1]) # True,淺拷貝可變對(duì)象得到的對(duì)象的可變子元素也是同一對(duì)象,這和深拷貝是不一樣的
# 下面兩個(gè)和上面兩個(gè)是一樣的情況,一一對(duì)應(yīng),只不過(guò)層級(jí)更深而已
print(b[1][0] is a[1][0]) # True
print(b[1][1] is a[1][1]) # True

b[0] = 111 # 對(duì)b[0]直接修改
b.append(3434) # 對(duì)b進(jìn)行追加操作
print(a, b) # 這里a和b不一樣了

b[1][0] = 22 # 對(duì)子元素b[1][0]直接修改
b[1].append(5) # 對(duì)子元素b[1]進(jìn)行追加操作
print(b[1], a[1]) # 這里的b[1]和a[1]還是保持一樣的

深拷貝

深拷貝使用copy.deepcopy(source)方法實(shí)現(xiàn),拷貝出來(lái)的對(duì)象和原對(duì)象有可能是同一對(duì)象,如果拷貝的對(duì)象是可變對(duì)象,其子元素也有可能是同一對(duì)象,但總的來(lái)說(shuō),這兩個(gè)對(duì)象完全沒(méi)關(guān)系(無(wú)論是本身還是其子對(duì)象都完全沒(méi)有關(guān)聯(lián)),操作一個(gè)不會(huì)影響到另一個(gè)

不可變對(duì)象的深拷貝
import copy
a = 'a' * 10000
b = copy.deepcopy(a)
print(b is a) # 輸出True,深拷貝不可變對(duì)象得到的對(duì)象和原對(duì)象是同一對(duì)象
可變對(duì)象的深拷貝
import copy
c = [1, [2, [3, [4]]]]
d = copy.deepcopy(c)
print(d is c) # 輸出False,深拷貝可變對(duì)象得到的對(duì)象和原對(duì)象是不同的對(duì)象
print(d[0] is c[0]) # 輸出True,深拷貝可變對(duì)象得到的對(duì)象的不可變子元素是同一對(duì)象
print(d[1] is c[1]) # 輸出False,深拷貝可變對(duì)象得到的對(duì)象的可變子元素是不同的對(duì)象
# 下面兩個(gè)和上面兩個(gè)是一樣的情況,一一對(duì)應(yīng),只不過(guò)層級(jí)更深而已
print(d[1][0] is c[1][0]) # 輸出True
print(d[1][1] is c[1][1]) # 輸出False

總結(jié)

  • 賦值不可變對(duì)象要看是否有緩存機(jī)制來(lái)決定是否是同一對(duì)象

  • 賦值可變對(duì)象相當(dāng)于引用,完全不拷貝

  • 切片相當(dāng)于淺拷貝

  • 對(duì)不可變對(duì)象進(jìn)行淺拷貝,相當(dāng)于深拷貝

  • 對(duì)可變對(duì)象進(jìn)行淺拷貝,直接修改一個(gè)不會(huì)不會(huì)影響另一個(gè),但對(duì)其可變子元素的修改會(huì)影響另一個(gè)

  • 深拷貝得到的對(duì)象和原對(duì)象互不相干,修改一個(gè)不會(huì)影響另一個(gè),這里指任何修改

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • http://python.jobbole.com/85231/ 關(guān)于專(zhuān)業(yè)技能寫(xiě)完項(xiàng)目接著寫(xiě)寫(xiě)一名3年工作經(jīng)驗(yàn)的J...
    燕京博士閱讀 7,806評(píng)論 1 118
  • Python 是一種相當(dāng)高級(jí)的語(yǔ)言,通過(guò) Python 解釋器把符合語(yǔ)法的程序代碼轉(zhuǎn)換成 CPU 能夠執(zhí)行的機(jī)器碼...
    Python程序媛閱讀 2,039評(píng)論 0 3
  • 一、Python簡(jiǎn)介和環(huán)境搭建以及pip的安裝 4課時(shí)實(shí)驗(yàn)課主要內(nèi)容 【Python簡(jiǎn)介】: Python 是一個(gè)...
    _小老虎_閱讀 6,339評(píng)論 0 10
  • 文/蔡哆嗦 就在昨天,在家呆了一個(gè)寒假的我極不情愿地來(lái)到學(xué)校,不知道為什么我們的內(nèi)在心理極其恐懼,或許我知道我只是...
    蔡哆嗦閱讀 537評(píng)論 0 7
  • 清冷的夜色,稀疏的路人,古城的氣息彌漫開(kāi)來(lái)~~ 閨蜜, 拿鐵, 靜靜的坐著, 已十分美好。
    卡布奇諾Coffee閱讀 420評(píng)論 0 0

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