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变量赋值中的浅拷贝和深拷贝概念。如果有疑问,可以进一步实验代码来加深理解!

相关推荐
森叶2 小时前
Java 比 Python 高性能的原因:重点在高并发方面
java·开发语言·python
xqhoj2 小时前
Linux——make、makefile
linux·运维·服务器
二哈喇子!2 小时前
Eclipse中导入外部jar包
java·eclipse·jar
微露清风2 小时前
系统性学习C++-第二十二讲-C++11
java·c++·学习
lifejump2 小时前
Pikachu | XXE
服务器·web安全·网络安全·安全性测试
Zoey的笔记本2 小时前
2026告别僵化工作流:支持自定义字段的看板工具选型与部署指南
大数据·前端·数据库
静听山水2 小时前
docker安装starrocks
数据库
进阶小白猿3 小时前
Java技术八股学习Day20
java·开发语言·学习
gis开发3 小时前
【无标题】
java·前端·javascript