Python中浅拷贝 VS 深拷贝
在说明Python中深拷贝与浅拷贝的知识点之前,咱们先来看一下昨天的知识点考察题:
a = [1, [2, 3]]
b = a.copy()
a[1].append(4)
print(b)
以上代码输出结果是什么?()
A. [1, [2, 3]]
B. [1, [2, 3, 4]]
C. [1, 2, 3, 4]
D. [1, 4, [2, 3]]
这道题的正确答案是:B
咱们来看下这段代码,
第一行:定义了一个变量a
,a的值是是一个嵌套列表
第二行:拷贝a的值,然后赋值给b
,此时b = [1, [2, 3]]
第三行:对嵌套列表a的索引为1的元素(即[2,3]
)进行末尾追加元素操作,此时a = [1, [2, 3, 4]]
第四行:打印输出b
那么,你是否会有疑惑:明明我是对a
进行的追加元素操作,为什么b
的值也同步改变了?
这其实就是Python中的浅拷贝陷阱
,学完今天的知识点后,你就会明白。
好的,下面让我们进入今天的知识点学习环节
在 Python 中,浅拷贝
和深拷贝
是两种对象复制机制,其核心区别在于对嵌套对象的处理方式。以下是详细解析:
浅拷贝
仅复制对象的最外层结构,嵌套的可变对象(如列表、字典)仍与原对象共享引用。修改浅拷贝中的嵌套对象会影响原对象。
实现方式:copy.copy()
、列表切片 list[:]
、字典的 dict.copy()
、 object.copy()
方法等 。
copy.copy() 方法:
属于 Python 内置模块方法,用于对象的浅拷贝,使用前需导入模块:
import copy
new_obj = copy.copy(old_obj)
深拷贝
通过递归复制对象及其所有嵌套对象,生成完全独立的副本。修改深拷贝的嵌套对象不会影响原对象。
实现方式:copy.deepcopy()
copy.deepcopy() 方法:
属于 Python 内置模块方法,用于对象的深拷贝,使用前需导入模块:
import copy
new_obj = copy.deepcopy(old_obj)
特性 | 浅拷贝 | 深拷贝 |
---|---|---|
复制层级 | 仅复制第一层对象 | 递归复制所有层级的嵌套对象 |
内存引用 | 嵌套可变对象共享原对象内存地址 | 所有层级对象均为独立内存空间 |
修改影响 | 修改嵌套对象会影响原对象 | 完全隔离,互不影响 |
适用场景 | 简单对象或不可变嵌套结构 | 复杂嵌套对象需完全独立 |
浅拷贝方法
copy.copy()
:通用的浅拷贝函数new_list = old_list[:]
dict.copy()
、list.copy()
object.copy()
:取决于具体的对象类型深拷贝方法
copy.deepcopy()
:唯一支持递归复制的函数,处理所有嵌套对象浅拷贝适用场景
深拷贝适用场景
不可变对象:若嵌套对象不可变(如元组),浅拷贝和深拷贝效果相同
易混淆知识:
a = b
是浅拷贝?:不是
,a = b
属于直接赋值(对象的引用),而非浅拷贝
# 把下面的代码复制进Python环境中运行,查看输出结果,加深理解
import copy
print("\nPython 浅拷贝和深拷贝的区别\n")
print(f"{' 浅拷贝 ':=^50}")
print("浅拷贝:只复制对象的引用,原对象和拷贝对象共享同一内存地址\n")
# 浅拷贝
list1 = [1, 2, 3, [4, 5]]
print(f"原列表:{list1}")
list2 = copy.copy(list1) # 使用 copy.copy() 进行浅拷贝
# 浅拷贝列表:浅拷贝只复制了第一层元素,第二层元素仍然指向同一对象
list2[0] = 10 # 修改第一层元素
print(f"{'修改第一层元素后':-^30}")
print(f"原列表:{list1}") # 输出: [1, 2, 3, [4, 5]]
print(f"浅拷贝列表:{list2}") # 输出: [10, 2, 3, [4, 5]]
list2[3][0] = 20 # 修改第二层元素
print(f"{'修改第二层元素后':-^30}")
print(f"原列表:{list1}") # 输出: [1, 2, 3, [20, 5]]
print(f"浅拷贝列表:{list2}") # 输出: [10, 2, 3, [20, 5]]
# 深拷贝
print(f"\n{' 深拷贝 ':=^50}")
print("深拷贝:复制对象及其所有子对象,原对象和拷贝对象不共享内存地址\n")
list1 = [1, 2, 3, [4, 5]]
print(f"原列表:{list1}")
list2 = copy.deepcopy(list1) # 使用 copy.deepcopy() 进行深拷贝
# 深拷贝列表:深拷贝会复制所有层级的元素
list2[0] = 10 # 修改第一层元素
print(f"{'修改第一层元素后':-^30}")
print(f"原列表:{list1}") # 输出: [1, 2, 3, [4, 5]]
print(f"深拷贝列表:{list2}") # 输出: [10, 2, 3, [4, 5]]
list2[3][0] = 20 # 修改第二层元素
print(f"{'修改第二层元素后':-^30}")
print(f"原列表:{list1}") # 输出: [1, 2, 3, [4, 5]]
print(f"深拷贝列表:{list2}") # 输出: [10, 2, 3, [20, 5]]
选择浅拷贝或深拷贝需根据数据结构和应用需求决定。
若需完全独立且不介意性能损耗,优先使用深拷贝;
若数据结构简单或需高效复制,浅拷贝更合适。
理解两者的区别能有效避免因共享引用导致的意外错误 。
在Python中“一切皆为对象”
(面向对象编程概念,先行了解)是核心设计理念,意味着所有数据类型、函数、类、模块甚至代码本身都是对象。这种设计赋予了Python极高的灵活性和动态性。作为对象,我们又可以将其细化分为:可变对象和不可变对象。
不可变对象:
修改
操作都会创建新对象。int
(整数)、float
(浮点数)、bool
(布尔值)、str
(字符串)、tuple
(元组)、frozenset
(不可变集合)等可变对象:
内存地址不变
。list
(列表)、dict
(字典)、set
(集合) 等核心区别对比
特性 | 不可变数据类型 | 可变数据类型 |
---|---|---|
修改操作 | 生成新对象 | 直接修改原对象 |
内存效率 | 频繁修改时会占用更多内存 | 适合频繁修改的场景 |
安全特性 | 线程安全,数据不易被意外修改 | 需注意共享引用的副作用 |
典型示例 | int , str , tuple |
list , dict , set |
可变性验证
id()
函数返回对象的唯一标识符,常用于验证对象身份:
# 不可变类型(字符串)
s = "abc"
print(f"原地址: {id(s)}") # 输出地址 A
s += "def" # 生成新对象
print(f"修改后地址: {id(s)}") # 输出新地址 B(与原地址不同)
# 可变类型(列表)
lst = [1, 2, 3]
print(f"原地址: {id(lst)}") # 输出地址 C
lst.append(4) # 直接修改原对象
print(f"修改后地址: {id(lst)}") # 地址仍是 C
通过理解可变与不可变对象的特性,可以避免常见错误(如默认参数陷阱),并优化内存使用和代码安全性。