0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線(xiàn)課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Python如何防止數(shù)據(jù)被修改Python中的深拷貝與淺拷貝的問(wèn)題說(shuō)明

馬哥Linux運(yùn)維 ? 來(lái)源:未知 ? 2019-03-30 09:54 ? 次閱讀

在平時(shí)工作中,經(jīng)常涉及到數(shù)據(jù)的傳遞。在數(shù)據(jù)傳遞使用過(guò)程中,可能會(huì)發(fā)生數(shù)據(jù)被修改的問(wèn)題。為了防止數(shù)據(jù)被修改,就需要再傳遞一個(gè)副本,即使副本被修改,也不會(huì)影響原數(shù)據(jù)的使用。為了生成這個(gè)副本,就產(chǎn)生了拷貝——今天就說(shuō)一下Python中的深拷貝與淺拷貝的問(wèn)題。

概念解讀

數(shù)據(jù)拷貝會(huì)涉及到Python中對(duì)象、可變類(lèi)型、引用這3個(gè)概念,先來(lái)看看這幾個(gè)概念,只有明白了它們才能更好地理解拷貝到底是怎么一回事。

Python對(duì)象

在Python中,對(duì)對(duì)象有一種很通俗的說(shuō)法,萬(wàn)物皆對(duì)象。說(shuō)的就是構(gòu)造的任何數(shù)據(jù)類(lèi)型都是一個(gè)對(duì)象,無(wú)論是數(shù)字、字符串、還是函數(shù),甚至是模塊、Python都對(duì)當(dāng)做對(duì)象處理。

所有Python對(duì)象都擁有三個(gè)屬性:身份、類(lèi)型、值。

看一個(gè)簡(jiǎn)單的例子:

In [1]: name ="laowang"# name對(duì)象In [2]: id(name) # id:身份的唯一標(biāo)識(shí)Out[2]: 1698668550104In [3]:type(name)# type:對(duì)象的類(lèi)型,決定了該對(duì)象可以保存什么類(lèi)型的值Out[3]: strIn [4]: name # 對(duì)象的值,表示的數(shù)據(jù)Out[4]:'laowang'

可變與不可變對(duì)象

在Python中,按更新對(duì)象的方式,可以將對(duì)象分為2大類(lèi):可變對(duì)象與不可變對(duì)象。

可變對(duì)象: 列表、字典、集合。所謂可變是指可變對(duì)象的值可變,身份是不變的。

不可變對(duì)象:數(shù)字、字符串、元組。不可變對(duì)象就是對(duì)象的身份和值都不可變。新創(chuàng)建的對(duì)象被關(guān)聯(lián)到原來(lái)的變量名,舊對(duì)象被丟棄,垃圾回收器會(huì)在適當(dāng)?shù)臅r(shí)機(jī)回收這些對(duì)象。

In [7]: var1 ="python"In [8]:id(var1)Out[8]:1700782038408#由于var1是不可變的,重新創(chuàng)建了java對(duì)象,隨之id改變,舊對(duì)象python會(huì)在某個(gè)時(shí)刻被回收In [9]: var1 ="java"In [10]:id(var1)Out[10]:1700767578296

引用

在Python程序中,每個(gè)對(duì)象都會(huì)在內(nèi)存中申請(qǐng)開(kāi)辟一塊空間來(lái)保存該對(duì)象,該對(duì)象在內(nèi)存中所在位置的地址被稱(chēng)為引用。在開(kāi)發(fā)程序時(shí),所定義的變量名實(shí)際就對(duì)象的地址引用。

引用實(shí)際就是內(nèi)存中的一個(gè)數(shù)字地址編號(hào),在使用對(duì)象時(shí),只要知道這個(gè)對(duì)象的地址,就可以操作這個(gè)對(duì)象,但是因?yàn)檫@個(gè)數(shù)字地址不方便在開(kāi)發(fā)時(shí)使用和記憶,所以使用變量名的形式來(lái)代替對(duì)象的數(shù)字地址。在Python中,變量就是地址的一種表示形式,并不開(kāi)辟開(kāi)辟存儲(chǔ)空間。

就像 IP 地址,在訪問(wèn)網(wǎng)站時(shí),實(shí)際都是通過(guò) IP 地址來(lái)確定主機(jī),而 IP 地址不方便記憶,所以使用域名來(lái)代替 IP 地址,在使用域名訪問(wèn)網(wǎng)站時(shí),域名被解析成 IP 地址來(lái)使用。

通過(guò)一個(gè)例子來(lái)說(shuō)明變量和變量指向的引用就是一個(gè)東西:

In [11]: age =18In [12]:id(age)Out[12]:1730306752In [13]:id(18)Out[13]:1730306752

逐步深入:引用賦值

上邊已經(jīng)明白,引用就是對(duì)象在內(nèi)存中的數(shù)字地址編號(hào),變量就是方便對(duì)引用的表示而出現(xiàn)的,變量指向的就是此引用。賦值的本質(zhì)就是讓多個(gè)變量同時(shí)引用同一個(gè)對(duì)象的地址。

那么在對(duì)數(shù)據(jù)修改時(shí)會(huì)發(fā)生什么問(wèn)題呢?

不可變對(duì)象的引用賦值

對(duì)不可變對(duì)象賦值,實(shí)際就是在內(nèi)存中開(kāi)辟一片空間指向新的對(duì)象,原不可變對(duì)象不會(huì)被修改。原理圖如下:

下面通過(guò)案例來(lái)理解一下:

a與b在內(nèi)存中都是指向1的引用,所以a、b的引用是相同的。

In [1]: a =1In [2]: b = aIn [3]:id(a)Out[3]:1730306496In [4]:id(b)Out[4]:1730306496

現(xiàn)在再給a重新賦值,看看會(huì)發(fā)生什么變化?從下面不難看出:當(dāng)給a賦新的對(duì)象時(shí),將指向現(xiàn)在的引用,不在指向舊的對(duì)象引用。

In [1]: a =1In [2]: b = aIn [5]: a =2In [6]:id(a)Out[6]:1730306816In [7]:id(b)Out[7]:1730306496

可變對(duì)象的引用賦值

可變對(duì)象保存的并不是真正的對(duì)象數(shù)據(jù),而是對(duì)象的引用。當(dāng)對(duì)可變對(duì)象進(jìn)行賦值時(shí),只是將可變對(duì)象中保存的引用指向了新的對(duì)象。原理圖如下:

仍然通過(guò)一個(gè)實(shí)例來(lái)體會(huì)一下,可變對(duì)象引用賦值的過(guò)程:當(dāng)改變l1時(shí),整個(gè)列表的引用會(huì)指新的對(duì)象,但是l1與l2都是指向保存的同一個(gè)列表的引用,所以引用地址不會(huì)變。

In [3]: l1 = [1,2,3]In [4]: l2 = l1In [5]:id(l1)Out[5]:1916633584008In [6]:id(l2)Out[6]:1916633584008In [7]: l1[0] =11In [8]:id(l1)Out[8]:1916633584008In [9]:id(l2)Out[9]:1916633584008

主旨詳解:淺拷貝、深拷貝

經(jīng)過(guò)前2部分的解讀,大家對(duì)對(duì)象的引用賦值應(yīng)該有了一個(gè)清晰的認(rèn)識(shí)了。那么Python中如何解決原始數(shù)據(jù)在函數(shù)傳遞之后不受影響?這個(gè)問(wèn)題Python已經(jīng)幫我們解決了,使用對(duì)象的拷貝或者深拷貝就可以愉快解決了。

下面具體來(lái)看看Python中的淺拷貝與深拷貝是如何實(shí)現(xiàn)的。

淺拷貝

為了解決函數(shù)傳遞后被修改的問(wèn)題,就需要拷貝一份副本,將副本傳遞給函數(shù)使用,就算是副本被修改,也不會(huì)影響原始數(shù)據(jù) 。

不可變對(duì)象的拷貝

不可變對(duì)象只在修改的時(shí)候才會(huì)在內(nèi)存中開(kāi)辟新的空間,而拷貝實(shí)際上是讓多個(gè)對(duì)象同時(shí)指向一個(gè)引用,和對(duì)象的賦值沒(méi)區(qū)別。

同樣的,通過(guò)一個(gè)實(shí)例來(lái)感受一下:不難看出,a與b指向相同的引用,不可變對(duì)象的拷貝就是對(duì)象賦值。

In [11]:importcopyIn [12]: a =10In [13]: b =copy.copy(a)In [14]: id(a)Out[14]:1730306496In [15]: id(b)Out[15]:1730306496

可變對(duì)象的拷貝

對(duì)于不可變對(duì)象的拷貝,對(duì)象的引用并沒(méi)有發(fā)生變化,那么可變對(duì)象的拷貝會(huì)不會(huì)和不可變對(duì)象一樣了?我們接著往下看。

通過(guò)下面的實(shí)例能看出:可變對(duì)象的拷貝會(huì)在內(nèi)存中開(kāi)辟一個(gè)新的空間來(lái)保存拷貝的數(shù)據(jù)。當(dāng)再改變之前的對(duì)象時(shí),對(duì)拷貝之后的對(duì)象沒(méi)有任何影響。

In [24]: importcopyIn [25]: l1 = [1,2,3]In [26]: l2 =copy.copy(l1)In [27]:id(l1)Out[27]:1916631742088In [28]:id(l2)Out[28]:1916636282952In [29]: l1[0] =11In [30]:id(l1)Out[30]:1916631742088In [31]:id(l2)Out[31]:1916636282952

原理圖如下:

現(xiàn)在再回到剛才那個(gè)問(wèn)題,是不是淺拷貝就可以解決原始數(shù)據(jù)在函數(shù)傳遞之后不變的問(wèn)題了?下面看一個(gè)稍微復(fù)雜一點(diǎn)的數(shù)據(jù)結(jié)構(gòu)。

通過(guò)下面這個(gè)實(shí)例可以發(fā)現(xiàn):復(fù)雜對(duì)象在拷貝時(shí),并沒(méi)有解決數(shù)據(jù)在傳遞之后,數(shù)據(jù)改變的問(wèn)題。出現(xiàn)這種原因,是copy() 函數(shù)在拷貝對(duì)象時(shí)只是將指定對(duì)象中的所有引用拷貝了一份,如果這些引用當(dāng)中包含了一個(gè)可變對(duì)象的話(huà),那么數(shù)據(jù)還是會(huì)被改變。這種拷貝方式,稱(chēng)為淺拷貝。

In [35]: a = [1,2]In [36]: l1 = [3,4, a]In [37]: l2 =copy.copy(l1)In [38]:id(l1)Out[38]:1916631704520In [39]:id(l2)Out[39]:1916631713736In [40]: a[0] =11In [41]:id(l1)Out[41]:1916631704520In [42]:id(l2)Out[42]:1916631713736In [43]: l1Out[43]: [3,4, [11,2]]In [44]: l2Out[44]: [3,4, [11,2]]

原理圖如下:

對(duì)于上邊這種狀況,Python還提供了另一種拷貝方式(深拷貝)來(lái)解決。

深拷貝

區(qū)別于淺拷貝只拷貝頂層引用,深拷貝會(huì)逐層進(jìn)行拷貝,直到拷貝的所有引用都是不可變引用為止。

接下來(lái)我們看看,要是將上邊的拷貝實(shí)例用使用深拷貝的話(huà),原始數(shù)據(jù)改變的問(wèn)題還會(huì)不會(huì)存在了?

下面的實(shí)例清楚地告訴我們:之前的問(wèn)題就可以完美解決了。

importcopyl1 = [3,4, a]In [47]: l2 =copy.deepcopy(li)In [48]:id(l1)Out[48]:1916632194312In [49]:id(l2)Out[49]:1916634281416In [50]: a[0] =11In [51]:id(l1)Out[51]:1916632194312In [52]:id(l2)Out[52]:1916634281416In [54]: l1Out[54]: [3,4, [11,2]]In [55]: l2Out[55]: [[1, 2], 3, 4]

原理圖如下:

查漏補(bǔ)缺

為什么Python默認(rèn)的拷貝方式是淺拷貝?

時(shí)間角度:淺拷貝花費(fèi)時(shí)間更少;

空間角度:淺拷貝花費(fèi)內(nèi)存更少;

效率角度:淺拷貝只拷貝頂層數(shù)據(jù),一般情況下比深拷貝效率高。

本文知識(shí)點(diǎn)總結(jié):

不可變對(duì)象在賦值時(shí)會(huì)開(kāi)辟新空間;

可變對(duì)象在賦值時(shí),修改一個(gè)的值,另一個(gè)也會(huì)發(fā)生改變;

深、淺拷貝對(duì)不可變對(duì)象拷貝時(shí),不開(kāi)辟新空間,相當(dāng)于賦值操作;

淺拷貝在拷貝時(shí),只拷貝第一層中的引用,如果元素是可變對(duì)象,并且被修改,那么拷貝的對(duì)象也會(huì)發(fā)生變化;

深拷貝在拷貝時(shí)會(huì)逐層進(jìn)行拷貝,直到所有的引用都是不可變對(duì)象為止;

Python中有多種方式實(shí)現(xiàn)淺拷貝,copy模塊的copy函數(shù)、對(duì)象的copy函數(shù)、工廠方法、切片等;

大多數(shù)情況下,編寫(xiě)程序時(shí)都是使用淺拷貝,除非有特定的需求;

淺拷貝的優(yōu)點(diǎn):拷貝速度快,占用空間少,拷貝效率高。

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 內(nèi)存
    +關(guān)注

    關(guān)注

    8

    文章

    3041

    瀏覽量

    74177
  • python
    +關(guān)注

    關(guān)注

    56

    文章

    4801

    瀏覽量

    84863

原文標(biāo)題:Python 程序員如何防止數(shù)據(jù)被修改?

文章出處:【微信號(hào):magedu-Linux,微信公眾號(hào):馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    Python面試必看的10個(gè)問(wèn)題

    吧。 1、Python里面如何拷貝一個(gè)對(duì)象?(賦值,拷貝,拷貝的區(qū)別)答:賦值(=),就是創(chuàng)
    發(fā)表于 02-28 17:00

    拷貝拷貝的實(shí)現(xiàn)方法概述

    拷貝拷貝的實(shí)現(xiàn)
    發(fā)表于 07-19 13:35

    python深淺拷貝是什么?

    python的直接賦值python拷貝python
    發(fā)表于 11-04 08:33

    請(qǐng)問(wèn)哪位大神可以詳細(xì)介紹JavaScript拷貝拷貝?

    JavaScript數(shù)據(jù)類(lèi)型JavaScript拷貝拷貝
    發(fā)表于 11-05 07:16

    功能函數(shù)數(shù)據(jù)拷貝

    第16章 DSP功能函數(shù)-數(shù)據(jù)拷貝,數(shù)據(jù)填充和浮點(diǎn)轉(zhuǎn)定點(diǎn)本期教程主要講解功能函數(shù)數(shù)據(jù)拷貝,
    發(fā)表于 08-17 07:41

    C#拷貝拷貝區(qū)別解析

     所謂拷貝就是將對(duì)象的所有字段復(fù)制到新的副本對(duì)象;拷貝對(duì)于值類(lèi)型與引用類(lèi)型的方式有區(qū)別,
    發(fā)表于 11-29 08:32 ?2.6w次閱讀
    C#<b class='flag-5'>淺</b><b class='flag-5'>拷貝</b>與<b class='flag-5'>深</b><b class='flag-5'>拷貝</b>區(qū)別解析

    實(shí)例介紹Python深淺拷貝

    【導(dǎo)語(yǔ)】:在工作,常涉及到數(shù)據(jù)的傳遞,在數(shù)據(jù)傳遞使用過(guò)程,可能會(huì)發(fā)生數(shù)據(jù)
    的頭像 發(fā)表于 12-16 11:34 ?1311次閱讀

    C++之拷貝構(gòu)造函數(shù)的copy及copy

    C++編譯器會(huì)默認(rèn)提供構(gòu)造函數(shù);無(wú)參構(gòu)造函數(shù)用于定義對(duì)象的默認(rèn)初始化狀態(tài);拷貝構(gòu)造函數(shù)在創(chuàng)建對(duì)象時(shí)拷貝對(duì)象的狀態(tài);對(duì)象的拷貝拷貝
    的頭像 發(fā)表于 12-24 15:31 ?773次閱讀

    簡(jiǎn)述Python深淺拷貝(copy)

    在工作,常涉及到數(shù)據(jù)的傳遞,在數(shù)據(jù)傳遞使用過(guò)程,可能會(huì)發(fā)生數(shù)據(jù)
    的頭像 發(fā)表于 07-29 16:55 ?1594次閱讀
    簡(jiǎn)述<b class='flag-5'>Python</b><b class='flag-5'>中</b>深淺<b class='flag-5'>拷貝</b>(copy)

    C++面向?qū)ο缶幊?b class='flag-5'>中拷貝拷貝

    可能對(duì)于Java程序員來(lái)說(shuō),很少遇到深淺拷貝問(wèn)題,但是對(duì)于C++程序員來(lái)說(shuō)可謂是又愛(ài)又恨。。
    的頭像 發(fā)表于 03-30 12:53 ?805次閱讀
    C++面向?qū)ο缶幊?b class='flag-5'>中</b>的<b class='flag-5'>深</b><b class='flag-5'>拷貝</b>和<b class='flag-5'>淺</b><b class='flag-5'>拷貝</b>

    C++拷貝拷貝詳解

    當(dāng)類(lèi)的函數(shù)成員存在指針成員時(shí)會(huì)產(chǎn)生拷貝拷貝和問(wèn)題。
    發(fā)表于 08-21 15:05 ?351次閱讀
    C++<b class='flag-5'>深</b><b class='flag-5'>拷貝</b>和<b class='flag-5'>淺</b><b class='flag-5'>拷貝</b>詳解

    Python拷貝拷貝的操作

    【例子】拷貝拷貝 list1 = [ 123 , 456 , 789 , 213 ]list2 = list1list3 = lis
    的頭像 發(fā)表于 11-02 10:58 ?423次閱讀

    pythontuple的用法

    Python的元組(tuple)是一種不可變的有序集合。與列表(list)類(lèi)似,元組可以存儲(chǔ)任意類(lèi)型的數(shù)據(jù),但是元組一旦創(chuàng)建就不能修改。
    的頭像 發(fā)表于 11-21 16:27 ?1030次閱讀

    什么是零拷貝技術(shù)

    在傳統(tǒng)操作系統(tǒng)的數(shù)據(jù)傳輸過(guò)程中,系統(tǒng)內(nèi)部會(huì)在磁盤(pán)、內(nèi)存、緩存多次進(jìn)行數(shù)據(jù)拷貝,每次都會(huì)占用CPU的資源,數(shù)據(jù)量小的時(shí)候還好。 隨著
    的頭像 發(fā)表于 11-27 16:20 ?474次閱讀
    什么是零<b class='flag-5'>拷貝</b>技術(shù)

    磁盤(pán)拷貝機(jī)會(huì)拷貝刪除的內(nèi)容嗎

    磁盤(pán)拷貝機(jī),也稱(chēng)為硬盤(pán)克隆器或磁盤(pán)復(fù)制器,是一種用于復(fù)制硬盤(pán)驅(qū)動(dòng)器內(nèi)容的設(shè)備。它可以將一個(gè)硬盤(pán)上的所有數(shù)據(jù),包括操作系統(tǒng)、程序、文件和設(shè)置,復(fù)制到另一個(gè)硬盤(pán)上。這種設(shè)備在數(shù)據(jù)備份、系統(tǒng)遷移、硬盤(pán)
    的頭像 發(fā)表于 10-14 15:38 ?530次閱讀