Python浅拷贝与深拷贝详解:原理、区别与copy模块用法
本文深入解析Python中浅拷贝与深拷贝的核心机制。通过代码示例对比直接赋值、浅拷贝与深拷贝在修改嵌套对象时的行为差异,详解copy模块中copy()与deepcopy()函数的实现原理及特殊场景(如元组处理)。文章澄清了“修改”与“重新赋值”对引用指向的影响,帮助开发者避免常见的内存引用陷阱。
一、问题引出:理解浅拷贝
首先观察以下代码的执行结果:
a = [1, 2, 3]
print('a = %s' % a) # a = [1, 2, 3]
b = a
print('b = %s' % b) # b = [1, 2, 3]
a.append(4) # 对a进行修改
print('a = %s' % a) # a = [1, 2, 3, 4]
print('b = %s' % b) # b = [1, 2, 3, 4]
b.append(5) # 对b进行修改
print('a = %s' % a) # a = [1, 2, 3, 4, 5]
print('b = %s' % b) # b = [1, 2, 3, 4, 5]
上述代码逻辑简单:定义一个包含数值[1, 2, 3]的列表变量a,并通过赋值语句b = a将变量b指向同一列表。
核心问题在于:若此时修改变量a,变量b是否受影响?反之,若修改变量b,变量a是否也会随之改变?
运行结果显示,无论是通过append方法直接修改a还是b(注意:此处为原地修改而非重新赋值),另一方均会同步发生变化。
其根本原因在于,变量a指向的是列表[1, 2, 3]在内存中的地址。当使用=进行赋值时,变量b同样指向该列表的内存地址。在Python中,可通过id(变量)函数查看内存地址。我们对比a和b的地址值,验证它们是否一致:
# 注意,不同机器上,这个值不同,但只要a,b两个变量的地址值是一样的就能说明问题了 print(id(a)) # 4439402312 print(id(b)) # 4439402312
因此,原理示意图如下所示:

结论是,只要对内存地址4439402312处的列表进行修改,变量a和b都会反映该变化。需注意的是,这里强调的是对“列表对象”本身的修改,而非对“变量a”的重新赋值(关于变量赋值的两种不同操作方式,将在文末详细解释)。基于此,我们引出以下概念:
这种将引用(即内存地址)直接拷贝给另一个变量的方式,被称为浅拷贝。
二、如何实现深拷贝
在Python中实现深拷贝十分简便,只需引入copy模块并调用其deepcopy()方法即可,示例代码如下:
import copy
a = [1, 2, 3]
b = copy.deepcopy(a)
print('a = %s' % a) # a = [1, 2, 3]
print('b = %s' % b) # b = [1, 2, 3]
b.append(4)
print('a = %s' % a) # a = [1, 2, 3]
print('b = %s' % b) # b = [1, 2, 3, 4]
从执行结果可见,深拷贝已成功实现。此时再对比两个变量的内存地址:
print(id(a)) # 4321416008 print(id(b)) # 4321416200
地址值确实不同。我们通过示意图进一步解析深拷贝的原理:

三、copy模块方法详解
通过深拷贝的实现过程,我们了解到copy模块提供了deepcopy()方法。接下来,我们将详细介绍copy模块中的copy()与deepcopy()两个核心方法。
首先回顾已使用的deepcopy()方法,官方文档说明如下:

对文档说明的简要解读:
1. 返回值为目标对象的深拷贝副本。
2. 若拷贝过程发生错误,将抛出copy.error异常。
3. 存在两个潜在问题:一是若遇到递归对象(嵌套引用),会进行递归拷贝;二是由于递归拷贝机制,可能导致拷贝层级过深或资源消耗过大。
4. 两种拷贝方式的区别主要针对引用对象而言。
前两点易于理解,针对第三点,我们通过代码进行验证:
import copy
a = [1, 2, 3]
b = [3, 4, 5]
c = [a, b] # 列表嵌套
d = copy.deepcopy(c)
print('c = %s' % c) # c = [[1, 2, 3], [3, 4, 5]]
print('d = %s' % d) # d = [[1, 2, 3], [3, 4, 5]]
c.append(4)
print('c = %s' % c) # c = [[1, 2, 3], [3, 4, 5], 4]
print('d = %s' % d) # d = [[1, 2, 3], [3, 4, 5]]
c[0].append(4) # 相当于a.append(4)
print('c = %s' % c) # c = [[1, 2, 3, 4], [3, 4, 5], 4]
print('d = %s' % d) # d = [[1, 2, 3], [3, 4, 5]]
# a.append(4)
# print('c = %s' % c) # a = [1, 2, 3]
# print('d = %s' % d) # b = [1, 2, 3]
print(id(c)) # 4314188040
print(id(d)) # 4314187976
print(id(c[0])) # 4314186568
print(id(d[0])) # 4314187912
print(id(a)) # 4314186568
print(id(b)) # 4314186760
代码显示,当存在嵌套对象(即文档所述的递归对象)时,嵌套对象会进行递归深拷贝。例如,若列表c包含列表a,则不仅c会被深拷贝,a也会被独立深拷贝。原理如下图所示:

接下来分析copy()方法:
官方文档指出,它返回对象的浅拷贝。但实际上,该方法对最外层对象执行深拷贝,而对第二层及更深层级的对象执行浅拷贝。代码示例如下:
import copy
a = [1, 2, 3]
b = [3, 4, 5]
c = [a, b] # 列表嵌套
d = copy.copy(c)
print('c = %s' % c) # c = [[1, 2, 3], [3, 4, 5]]
print('d = %s' % d) # d = [[1, 2, 3], [3, 4, 5]]
c.append(4)
print('c = %s' % c) # c = [[1, 2, 3], [3, 4, 5], 4]
print('d = %s' % d) # d = [[1, 2, 3], [3, 4, 5]] 没有发生变化,说明外层是深拷贝
c[0].append(4) # 相当于a.append(4)
print('c = %s' % c) # c = [[1, 2, 3, 4], [3, 4, 5], 4]
print('d = %s' % d) # d = [[1, 2, 3, 4], [3, 4, 5]] 发生了变化,说明内层是浅拷贝
# a.append(4)
# print('c = %s' % c) # c = [[1, 2, 3, 4], [3, 4, 5], 4]
# print('d = %s' % d) # d = [[1, 2, 3, 4], [3, 4, 5]] 发生了变化,说明内层是浅拷贝
print(id(c)) # 4322576648
print(id(d)) # 4322576584 d和c地址不同,进一步说明外层是深拷贝
print(id(c[0])) # 4322575176
print(id(d[0])) # 4322575176 c[0]和d[0]地址相同,进一步说明内层是浅拷贝
print(id(a)) # 4322575176
print(id(b)) # 4322575368
【注意】对于copy()方法存在特殊情况,例如处理元组类型时,代码示例如下:
import copy a = [1, 2, 3] b = [3, 4, 5] c = (a, b) # 列表改成元组 d = copy.copy(c) print(id(c)) # 4303015752 print(id(d)) # 4303015752 d和c地址相同 print(id(c[0])) # 4322575176 print(id(d[0])) # 4322575176 c[0]和d[0]地址相同,进一步说明内层是浅拷贝
结果显示,即使是最外层,也表现为浅拷贝。
这是因为copy方法内部包含类型判断:若最外层为不可变类型,则执行浅拷贝;否则执行深拷贝。
至此,我们对浅拷贝概念可进一步总结:
若对象中任一元素采用引用拷贝,则该整体定义为浅拷贝。(注:此为非官方定义的个人理解)
四、关于“修改”操作的补充说明
前文提及的“修改变量”实际包含两种操作:一是在原对象上修改,二是重新赋值。请看以下代码:
import copy a = [1, 2, 3] b = a a = [3, 4, 5] print(a) # [3, 4, 5] print(b) # [1, 2, 3]
同为浅拷贝场景,但修改a后,b并未发生变化。
在修改操作中,人们常习惯通过重新赋值来改变变量值,但这存在误区。当对a重新赋值时,a实际上指向了新的内存区域,而原列表[1, 2, 3]所在的内存区域并未改变。因此,对于仍指向原地址的b而言,其指向的内容未变。
这也解释了为何deepcopy方法说明中强调“仅对引用对象有效”:因为简单类型的修改通常是通过重新赋值实现的,这会导致指向新地址。简言之,无法通过简单类型变量直接调用自身方法进行修改,只能通过重新赋值改变指向,从而指向新地址。

以上即为Python核心编程中关于浅拷贝与深拷贝的全部内容。欢迎将学习心得留言于下方,感谢对本站的支持。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













