Python - 对象赋值、浅拷贝、深拷贝的区别
<h3>前言</h3><ul>
<li>Python 中不存在值传递,一切传递的都是对象的引用,也可以认为是传址</li>
<li>这里会讲三个概念:对象赋值、浅拷贝、深拷贝</li>
</ul>
<p> </p>
<h3>名词解释</h3>
<ul>
<li>变量:存储对象的引用</li>
<li>对象:会被分配一块内存,存储实际的数据,比如字符串、数字、列表</li>
<li>引用:变量指向对象,可以理解为指针</li>
</ul>
<p><img src="https://img2020.cnblogs.com/blog/1896874/202107/1896874-20210731194409686-1313532978.png" alt="" loading="lazy"></p>
<p> </p>
<h3>实际的一种应用场景</h3>
<ul>
<li>有一个变量 a,存储了一个值</li>
<li>此时想用另一个变量 b 暂时存储变量 a 的值,以便后续使用</li>
<li>然后继续修改变量 a 的值,但修改的时候并不想同步更改变量 b 的值</li>
</ul>
<div class="cnblogs_code">
<pre>a=1<span style="color: rgba(0, 0, 0, 1)">
b</span>=<span style="color: rgba(0, 0, 0, 1)">a
a</span>=2</pre>
</div>
<p> </p>
<h3>对象赋值</h3>
<ul>
<li>赋值运算符详解:https://www.cnblogs.com/poloyy/p/15083012.html</li>
<li>Python 的赋值语句并不是创建一个新对象,只是创建了一个共享原始对象引用的新变量</li>
</ul>
<p> </p>
<h3>不可变对象的赋值</h3>
<div class="cnblogs_code">
<pre>a = 1<span style="color: rgba(0, 0, 0, 1)">
b </span>=<span style="color: rgba(0, 0, 0, 1)"> a
</span><span style="color: rgba(0, 0, 255, 1)">print</span><span style="color: rgba(0, 0, 0, 1)">(a, b)
a +</span>= 2
<span style="color: rgba(0, 0, 255, 1)">print</span><span style="color: rgba(0, 0, 0, 1)">(a, b)
</span><span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">a id:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, id(a))
</span><span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">b id:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, id(b))
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 输出结果</span>
1 1
3 1<span style="color: rgba(0, 0, 0, 1)">
a id: </span>4564097808<span style="color: rgba(0, 0, 0, 1)">
b id: </span>4564097776</pre>
</div>
<ul>
<li>修改变量 a 的值,<span style="color: rgba(210, 44, 74, 1)">不会同步</span>修改变量 b 的值</li>
<li>因为赋值操作 <span class="cnblogs_code">a += 2</span> 后,变量 a 存储的对象引用已经改变了</li>
<li>至于具体的原理,可以看看不可变对象、可变对象的详解 https://www.cnblogs.com/poloyy/p/15073168.html</li>
</ul>
<p> </p>
<h3>可变对象的赋值</h3>
<div class="cnblogs_code">
<pre>a =
b </span>=<span style="color: rgba(0, 0, 0, 1)"> a
</span><span style="color: rgba(0, 0, 255, 1)">print</span><span style="color: rgba(0, 0, 0, 1)">(a, b)
a[</span>1] = 22
<span style="color: rgba(0, 0, 255, 1)">print</span><span style="color: rgba(0, 0, 0, 1)">(a, b)
</span><span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">a id:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, id(a))
</span><span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">b id:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, id(b))
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 输出结果</span>
[</span>1, 22, 3]
a id: </span>4567683136<span style="color: rgba(0, 0, 0, 1)">
b id: </span>4567683136</pre>
</div>
<ul>
<li>修改 a 变量的值,<span style="color: rgba(210, 44, 74, 1)">会同步</span>修改变量 b 的值,这不符合上面的说的实际应用场景</li>
<li>因为变量 a、b 指向的对象是可变对象,所以它们保存的对象引用都是一样的</li>
</ul>
<p> </p>
<h4>拷贝的诞生</h4>
<ul>
<li>那如果要让可变对象也能满足上述实际应用场景,要怎么做呢?</li>
<li>当然就是<span style="color: rgba(210, 44, 74, 1)">拷贝</span></li>
<li>而拷贝又分为<span style="color: rgba(210, 44, 74, 1)">浅拷贝、深拷贝</span>,接下来会具体聊一聊两种拷贝的区别</li>
</ul>
<p> </p>
<h3><span style="color: rgba(210, 44, 74, 1)">第一个重点总结</span></h3>
<ul>
<li>对于不可变对象来说,赋值操作其实就可以满足上面说的实际应用场景</li>
<li>所以!后面要讲的浅拷贝、深拷贝对于不可变对象来说,和赋值操作是一样的效果!</li>
<li>记住!浅拷贝、深拷贝<span style="color: rgba(210, 44, 74, 1)"><strong>只针对</strong></span>可变对象,即列表、集合、字典!</li>
</ul>
<p> </p>
<h3>copy 模块</h3>
<p>Python 提供了 copy 模块,包含了浅拷贝、深拷贝函数</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">from</span> copy <span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> copy, deepcopy
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 浅拷贝</span>
<span style="color: rgba(0, 0, 0, 1)">copy(x)
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 深拷贝</span>
deepcopy(x)</pre>
</div>
<p> </p>
<h3>浅拷贝</h3>
<p><strong><span style="color: rgba(210, 44, 74, 1)">一句话概括:</span></strong>浅拷贝会创建一个<span style="color: rgba(210, 44, 74, 1)">新对象</span>,该新对象存储<span style="color: rgba(210, 44, 74, 1)">原始元素</span>的引用</p>
<p> </p>
<h4>浅拷贝后的值是相同的</h4>
<ul>
<li>将列表赋值给变量 old_list</li>
<li>通过 copy() 方法对 old_list 变量指向的对象进行浅拷贝,并赋值给新变量 new_list</li>
<li>因为是对象进行拷贝,所以 new_list 和 old_list 存储的值是相同的</li>
</ul>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> copy
old_list </span>= [, , ]
new_list </span>=<span style="color: rgba(0, 0, 0, 1)"> copy.copy(old_list)
</span><span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Old list:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, old_list)
</span><span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">New list:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, new_list)
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 输出结果</span>
Old list: [, , ]
New list: [[</span>1, 2, 3], , ]</pre>
</div>
<p> </p>
<h4>浅拷贝后的会产生一个新的对象</h4>
<ul>
<li>虽然 old_list 和 new_list 存储的值是相同的,但浅拷贝的操作是产生了一个新的对象</li>
<li>所以 old_list 和 new_list 指向的对象并不是同一个</li>
</ul>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> copy
old_list </span>= [, ]
new_list </span>=<span style="color: rgba(0, 0, 0, 1)"> copy.copy(old_list)
old_list.append([</span>5, 6<span style="color: rgba(0, 0, 0, 1)">])
</span><span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Old list:</span><span style="color: rgba(128, 0, 0, 1)">"</span>, old_list, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">id is :</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, id(old_list))
</span><span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">New list:</span><span style="color: rgba(128, 0, 0, 1)">"</span>, new_list, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">id is :</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, id(new_list))
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 输出结果</span>
Old list: [, , ] id <span style="color: rgba(0, 0, 255, 1)">is</span> : 4366240704<span style="color: rgba(0, 0, 0, 1)">
New list: [[</span>1, 2], ] id <span style="color: rgba(0, 0, 255, 1)">is</span> : 4366246720</pre>
</div>
<p>可以看到内存地址是不同的,所以给 old_list 新增一个元素并不会同步让 new_list 也新增</p>
<p> </p>
<h4>原理图</h4>
<p><img src="https://img2020.cnblogs.com/blog/1896874/202108/1896874-20210801103442325-199330745.png" alt="" loading="lazy"></p>
<ul>
<li>浅拷贝生成了一个新对象,然后赋值给 new_list</li>
<li>new_list、old_list 指向的列表对象不是同一个,但值相同</li>
<li><span style="color: rgba(210, 44, 74, 1)">重点:</span>对于列表对象中的元素,浅拷贝产生的新对象只存储原始元素的引用(内存地址),所以两个列表对象的元素的引用都指向同一个内存地址</li>
</ul>
<p> </p>
<h3>那为什么要深拷贝呢?</h3>
<h4>修改列表内的不可变对象元素</h4>
<p>上面的栗子是直接添加元素,来看看修改元素会怎么样</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 不可变元素</span>
<span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> copy
old_list </span>=
new_list </span>=<span style="color: rgba(0, 0, 0, 1)"> copy.copy(old_list)
old_list[</span>1] += 22<span style="color: rgba(0, 0, 0, 1)">
old_list[</span>2] += <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">s</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
old_list[</span>3] += (3<span style="color: rgba(0, 0, 0, 1)">,)
</span><span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Old list:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, old_list)
</span><span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">New list:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, new_list)
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 输出结果</span>
Old list:
New list: [</span>1, 2, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">string</span><span style="color: rgba(128, 0, 0, 1)">'</span>, (1, 2)]</pre>
</div>
<p>修改 old_list 的三种不可变对象元素,均不会同步给 new_list</p>
<p> </p>
<h4>修改不可变对象的原理图</h4>
<p><img src="https://img2020.cnblogs.com/blog/1896874/202108/1896874-20210801105557786-1875694485.png" alt="" loading="lazy"></p>
<p> </p>
<h4>修改列表内的可变对象元素</h4>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 可变元素</span>
<span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> copy
old_list </span>= [, ]
new_list </span>=<span style="color: rgba(0, 0, 0, 1)"> copy.copy(old_list)
old_list </span>+= 99<span style="color: rgba(0, 0, 0, 1)">
old_list[</span>1] += 97
<span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Old list:</span><span style="color: rgba(128, 0, 0, 1)">"</span>, old_list, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">old list id:</span><span style="color: rgba(128, 0, 0, 1)">"</span>, id(old_list), <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)"> old list id:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, id(old_list))
</span><span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">new list:</span><span style="color: rgba(128, 0, 0, 1)">"</span>, new_list, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">new list id:</span><span style="color: rgba(128, 0, 0, 1)">"</span>, id(new_list), <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)"> new list id:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, id(new_list))
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 输出结果</span>
Old list: [, ] old list id: 4430308096old list id: 4430302400<span style="color: rgba(0, 0, 0, 1)">
new list: [[</span>100, 2], ] new list id: 4430308416new list id: 4430302400</pre>
</div>
<p>从输出结果看到</p>
<ul>
<li>两个变量保存了<span style="color: rgba(210, 44, 74, 1)">不同的</span>对象引用</li>
<li>但是可变对象元素的内存地址仍然是<span style="color: rgba(210, 44, 74, 1)">同一个</span></li>
</ul>
<p> </p>
<h4>修改可变对象的原理图</h4>
<p><img src="https://img2020.cnblogs.com/blog/1896874/202108/1896874-20210801110731999-1822292487.png" alt="" loading="lazy"></p>
<p> </p>
<h4>总结</h4>
<ul>
<li>修改可变对象是在原始对象上直接操作的</li>
<li>浅拷贝产生的新对象存储的仍然是原始对象的内存地址</li>
<li>所以修改可变对象的时候,新对象的值也会被同步修改,因为新旧列表对象的元素的引用是指向同一个内存地址</li>
<li>当修改可变对象的时候,不满足一开始说的实际应用场景,所以诞生了深拷贝</li>
</ul>
<p> </p>
<h3>深拷贝</h3>
<ul>
<li>创建一个新对象,且存储的对象引用也是新的</li>
<li>深,意味着会把所有子元素对象也复制生成一个新对象</li>
</ul>
<p> </p>
<h4>栗子一</h4>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 深拷贝</span>
old_list = [, ]
new_list </span>=<span style="color: rgba(0, 0, 0, 1)"> copy.deepcopy(old_list)
old_list </span>+= 99<span style="color: rgba(0, 0, 0, 1)">
old_list[</span>1] += 97
<span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Old list:</span><span style="color: rgba(128, 0, 0, 1)">"</span>, old_list, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">old list id:</span><span style="color: rgba(128, 0, 0, 1)">"</span>, id(old_list), <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)"> old list id:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, id(old_list))
</span><span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">new list:</span><span style="color: rgba(128, 0, 0, 1)">"</span>, new_list, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">new list id:</span><span style="color: rgba(128, 0, 0, 1)">"</span>, id(new_list), <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)"> new list id:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, id(new_list))
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 输出结果</span>
Old list: [, ] old list id: 4430308480old list id: 4430211392<span style="color: rgba(0, 0, 0, 1)">
new list: [[</span>1, 2], ] new list id: 4430308096new list id: 4430308864</pre>
</div>
<p>从输出结果看到</p>
<ul>
<li>两个变量保存了<span style="color: rgba(210, 44, 74, 1)">不同的</span>对象引用</li>
<li>可变对象元素(子对象)的内存地址也是<span style="color: rgba(210, 44, 74, 1)">不同的</span></li>
</ul>
<p> </p>
<h4>栗子二</h4>
<p>假设是一个三维列表呢</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 深拷贝-三维数组</span>
old_list = [], ]
new_list </span>=<span style="color: rgba(0, 0, 0, 1)"> copy.deepcopy(old_list)
old_list[</span>1] += 90
<span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Old list:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, old_list)
</span><span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">New list:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, new_list)
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 输出结果</span>
Old list: [], ]
New list: [[</span>1, ], ]</pre>
</div>
<p>两个变量依旧是独立的</p>
<p> </p>
<h4>深拷贝原理图</h4>
<p><img src="https://img2020.cnblogs.com/blog/1896874/202108/1896874-20210801124730663-364174440.png" alt="" loading="lazy"></p>
<p> </p>
<h3>浅拷贝的多种实现方式</h3>
<p>https://www.cnblogs.com/poloyy/p/15086511.html</p>
<p> </p>
<h3>面试题:浅拷贝、深拷贝的区别</h3>
<ol>
<li>浅拷贝和深拷贝只有在可变对象才会生效,不可变对象的赋值操作、浅拷贝、深拷贝的效果是一样的</li>
<li>浅拷贝会将对象复制生成一个新对象,但新对象仍然存储原始对象的引用,当原始对象是可变对象,然后修改它的值时,新旧对象会同时改变</li>
<li>深拷贝不仅会将对象复制生成一个新对象,且所有原始对象都会复制生成新对象,即使原始对象是可变对象,新对象存储的对象引用也是新的,所以改变旧对象的可变对象时,不会影响新对象</li>
</ol><br><br>
来源:https://www.cnblogs.com/poloyy/p/15084277.html
頁:
[1]