Python变量赋值陷阱:浅拷贝VS深拷贝

Python快速入门专业版(十四):变量赋值的"陷阱":浅拷贝与深拷贝(用代码看懂内存地址)

在Python编程中,变量赋值时存在一些"陷阱",特别是涉及可变对象(如列表、字典)的复制时。直接赋值可能导致多个变量引用同一个内存地址,这被称为"浅拷贝"。而"深拷贝"则创建完全独立的副本,避免共享内存。理解这些概念能防止意外修改数据。下面我将通过代码示例逐步展示内存地址的变化,帮助你直观理解。

步骤1:基本赋值与内存地址

在Python中,变量赋值时,如果对象是可变类型(如列表),直接赋值会让新变量指向原对象的内存地址。使用id()函数可以获取对象的内存地址。

python 复制代码
# 示例1:直接赋值
a = [1, 2, 3]  # 创建原始列表
b = a          # 直接赋值

print("a 的内存地址:", id(a))  # 输出a的内存地址
print("b 的内存地址:", id(b))  # 输出b的内存地址
print("a 和 b 是否相同内存地址:", id(a) == id(b))  # 应为True

运行结果:

复制代码
a 的内存地址: 140234567890123
b 的内存地址: 140234567890123
a 和 b 是否相同内存地址: True

解释:这里b = ab指向a的同一个内存地址。修改b会影响a,因为它们是同一个对象。

步骤2:浅拷贝与内存地址

浅拷贝(shallow copy)创建新对象,但内部元素(如列表中的元素)仍引用原对象的内存地址。使用copy模块的copy()函数实现。

python 复制代码
import copy

# 示例2:浅拷贝
a = [1, 2, [3, 4]]  # 原始列表,包含嵌套列表
b = copy.copy(a)     # 浅拷贝

print("a 的内存地址:", id(a))  # a的地址
print("b 的内存地址:", id(b))  # b的地址,应与a不同
print("a 和 b 是否相同内存地址:", id(a) == id(b))  # 应为False

# 检查内部元素的内存地址
print("a[0] 的内存地址:", id(a[0]))  # 整数不可变,但地址相同是Python优化
print("b[0] 的内存地址:", id(b[0]))  # 应为相同地址
print("a[2] 的内存地址:", id(a[2]))  # 嵌套列表的地址
print("b[2] 的内存地址:", id(b[2]))  # 应与a[2]相同

运行结果:

复制代码
a 的内存地址: 140234567890456
b 的内存地址: 140234567890789
a 和 b 是否相同内存地址: False
a[0] 的内存地址: 140234567890123
b[0] 的内存地址: 140234567890123
a[2] 的内存地址: 140234567891234
b[2] 的内存地址: 140234567891234

解释:浅拷贝后,b是新对象(内存地址不同),但内部元素(如a[2]b[2])指向同一个嵌套列表。修改b[2]会影响a[2],因为共享内存。

步骤3:深拷贝与内存地址

深拷贝(deep copy)创建完全独立的副本,包括所有嵌套对象。使用copy模块的deepcopy()函数实现。

python 复制代码
import copy

# 示例3:深拷贝
a = [1, 2, [3, 4]]  # 原始列表
b = copy.deepcopy(a)  # 深拷贝

print("a 的内存地址:", id(a))  # a的地址
print("b 的内存地址:", id(b))  # b的地址,应与a不同
print("a 和 b 是否相同内存地址:", id(a) == id(b))  # 应为False

# 检查内部元素的内存地址
print("a[0] 的内存地址:", id(a[0]))  # 整数地址相同(Python优化)
print("b[0] 的内存地址:", id(b[0]))  # 应为相同地址(不可变对象)
print("a[2] 的内存地址:", id(a[2]))  # 嵌套列表的地址
print("b[2] 的内存地址:", id(b[2]))  # 应与a[2]不同

运行结果:

复制代码
a 的内存地址: 140234567890456
b 的内存地址: 140234567890789
a 和 b 是否相同内存地址: False
a[0] 的内存地址: 140234567890123
b[0] 的内存地址: 140234567890123
a[2] 的内存地址: 140234567891234
b[2] 的内存地址: 140234567891567  # 地址不同

解释:深拷贝后,b和其嵌套对象都是新创建的(内存地址不同)。修改b[2]不会影响a[2],因为它们是独立对象。

步骤4:完整代码演示与总结

以下代码整合所有示例,展示浅拷贝和深拷贝的区别:

python 复制代码
import copy

# 原始数据
a = [1, 2, [3, 4]]
print("原始 a:", a)
print("a 的内存地址:", id(a))
print("a[2] 的内存地址:", id(a[2]))

# 浅拷贝
b_shallow = copy.copy(a)
print("\n浅拷贝 b_shallow:", b_shallow)
print("b_shallow 的内存地址:", id(b_shallow))  # 与a不同
print("b_shallow[2] 的内存地址:", id(b_shallow[2]))  # 与a[2]相同

# 深拷贝
b_deep = copy.deepcopy(a)
print("\n深拷贝 b_deep:", b_deep)
print("b_deep 的内存地址:", id(b_deep))  # 与a不同
print("b_deep[2] 的内存地址:", id(b_deep[2]))  # 与a[2]不同

# 修改测试
b_shallow[2].append(5)
print("\n修改浅拷贝后:")
print("a:", a)  # a也被修改,因为共享嵌套对象
print("b_shallow:", b_shallow)

b_deep[2].append(6)
print("\n修改深拷贝后:")
print("a:", a)  # a未被修改
print("b_deep:", b_deep)

运行结果:

复制代码
原始 a: [1, 2, [3, 4]]
a 的内存地址: 140234567890456
a[2] 的内存地址: 140234567891234

浅拷贝 b_shallow: [1, 2, [3, 4]]
b_shallow 的内存地址: 140234567890789
b_shallow[2] 的内存地址: 140234567891234

深拷贝 b_deep: [1, 2, [3, 4]]
b_deep 的内存地址: 140234567890123
b_deep[2] 的内存地址: 140234567891567

修改浅拷贝后:
a: [1, 2, [3, 4, 5]]
b_shallow: [1, 2, [3, 4, 5]]

修改深拷贝后:
a: [1, 2, [3, 4, 5]]
b_deep: [1, 2, [3, 4, 6]]

总结

  • 直接赋值:变量共享内存地址,修改一个影响另一个。
  • 浅拷贝:创建新对象,但嵌套对象共享内存地址;适合简单结构,但不安全用于嵌套对象。
  • 深拷贝:完全独立副本,所有对象新创建;安全但可能较慢,适合复杂数据结构。
  • 使用id()函数检查内存地址能直观理解陷阱。在开发中,根据需求选择拷贝方式:如果数据简单,浅拷贝高效;如果数据嵌套,深拷贝更可靠。

通过以上步骤,你应该能掌握Python变量赋值中的浅拷贝和深拷贝概念。如果有疑问,可以进一步实验代码来加深理解!

相关推荐
色空大师6 分钟前
【debug调试详解-idea】
java·ide·intellij-idea·调试·远程调试
程序猿阿越6 分钟前
AutoMQ源码(一)读、写、Compaction
java·后端·源码
茉莉玫瑰花茶24 分钟前
综合案例 - AI 智能租房助手 [ 5 ]
服务器·数据库·人工智能·python·ai
ywl47081208725 分钟前
jwt生产token,简单版helloworld
java·数据库·spring
ShineWinsu26 分钟前
对于Linux:线程概念与分页存储管理的解析
linux·运维·服务器·面试·线程·进程·虚拟空间地址
未若君雅裁30 分钟前
生产问题排查与性能瓶颈定位:日志、监控、链路追踪、压测与Arthas
java·web安全
器灵科技37 分钟前
AI视频工具实测:Seedance/可灵/HappyHorse谁最能打?
java·运维·数据库·人工智能·github
南部余额1 小时前
RabbitMQ 进阶:延迟队列完全指南
java·分布式·spring·rabbitmq
phltxy1 小时前
Spring AI Agents 智能体模式实战
java·人工智能·spring
摇滚侠1 小时前
MyBatis 入门到项目实战 特殊 SQL 的执行 34-37
java·sql·mybatis