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