浅拷贝创建新容器但共享内部元素,深拷贝递归复制所有层级确保完全独立。Python中通过切片、copy()实现浅拷贝,copy.deepcopy()实现深拷贝,前者高效但修改嵌套可变元素会影响原对象,后者开销大但隔离彻底。

Python中的浅拷贝与深拷贝,核心在于它们处理复合对象内部元素的方式不同。简单来说,浅拷贝创建了一个新的容器对象,但其内部元素(如果也是对象的话)仍然是原对象的引用;而深拷贝则会递归地创建所有内部元素的独立副本,确保新旧对象之间完全独立。
在Python中,我们经常需要复制一个对象。直接赋值(
b = a
b
a
浅拷贝,顾名思义,就是只复制了对象“最外层”的结构。如果你有一个列表,里面装着数字和另一个列表,浅拷贝会给你一个新的外层列表,但那个内部的列表,新旧对象还是共享同一个。
你可以通过几种方式实现浅拷贝:
立即学习“Python免费学习笔记(深入)”;
切片操作 [:]
import copy
original_list = [1, [2, 3], 4]
shallow_copy_slice = original_list[:]
print(f"原始列表ID: {id(original_list)}, 浅拷贝列表ID: {id(shallow_copy_slice)}")
# 输出ID不同,说明是两个不同的列表对象
print(f"原始列表内部列表ID: {id(original_list[1])}, 浅拷贝内部列表ID: {id(shallow_copy_slice[1])}")
# 输出ID相同,说明内部列表是共享的list()
dict()
original_dict = {'a': 1, 'b': [2, 3]}
shallow_copy_constructor = dict(original_dict)
print(f"原始字典ID: {id(original_dict)}, 浅拷贝字典ID: {id(shallow_copy_constructor)}")
print(f"原始字典内部列表ID: {id(original_dict['b'])}, 浅拷贝内部列表ID: {id(shallow_copy_constructor['b'])}")copy.copy()
copy
__copy__
original_set = {1, 2, 3}
shallow_copy_set = copy.copy(original_set)
# 对于集合这种非复合结构,浅拷贝和深拷贝行为上无差异,因为元素是不可变的。
# 但如果集合元素是可变对象,那就有区别了。当浅拷贝的内部元素是可变对象时,问题就来了。修改浅拷贝内部的可变对象,原对象也会跟着变,反之亦然。这在调试时可能会让人抓狂,因为数据来源变得模糊不清。
original = [1, [2, 3], 4]
shallow_copy = original[:]
shallow_copy[0] = 100 # 修改顶层元素,不影响original
shallow_copy[1][0] = 99 # 修改内部可变元素,会影响original
print(f"Original after shallow copy modification: {original}") # Output: [1, [99, 3], 4]
print(f"Shallow copy after modification: {shallow_copy}") # Output: [100, [99, 3], 4]你看,
original
深拷贝则完全不同,它会递归地复制对象及其所有子对象,直到所有元素都是独立的。这意味着你对深拷贝的任何修改都不会影响到原对象,它们是两个完全独立的“王国”。
实现深拷贝主要依赖
copy
copy.deepcopy()
import copy
original = [1, [2, 3], 4]
deep_copy = copy.deepcopy(original)
print(f"原始列表ID: {id(original)}, 深拷贝列表ID: {id(deep_copy)}")
print(f"原始列表内部列表ID: {id(original[1])}, 深拷贝内部列表ID: {id(deep_copy[1])}")
# 输出ID不同,说明内部列表也是独立的
deep_copy[0] = 100
deep_copy[1][0] = 99
print(f"Original after deep copy modification: {original}") # Output: [1, [2, 3], 4]
print(f"Deep copy after modification: {deep_copy}") # Output: [100, [99, 3], 4]这次,无论怎么修改
deep_copy
original
说实话,很多人在遇到需要复制对象时,第一反应可能就是“深拷贝万岁”,觉得这样最安全。但浅拷贝在某些特定场景下,反而更高效、更合理。
一个常见的场景是,当你只需要创建一个新的顶层容器,而内部的元素是不可变对象(如数字、字符串、元组),或者你有意图让新旧容器共享内部可变对象时。比如,你有一个用户列表,每个用户对象可能包含一些不变的ID和名字,以及一个可变的“购物车”列表。如果你只是想创建一个新的用户列表,但希望所有用户对象仍然是内存中的同一个,只是列表的顺序或者增减用户不同,那么浅拷贝就足够了。
class User:
def __init__(self, user_id, name, cart):
self.user_id = user_id
self.name = name
self.cart = cart # 购物车是一个可变列表
def __repr__(self):
return f"User(id={self.user_id}, name='{self.name}', cart={self.cart})"
user1 = User(1, "Alice", ["apple", "banana"])
user2 = User(2, "Bob", ["orange"])
all_users = [user1, user2]
# 浅拷贝用户列表
new_users_list = all_users[:]
new_users_list.append(User(3, "Charlie", [])) # 新增一个用户,不影响all_users
print(f"Original users: {all_users}")
print(f"New users list: {new_users_list}")
# 此时,user1和user2对象本身在两个列表中是共享的
user1.cart.append("grape") # 修改user1的购物车,会影响all_users和new_users_list中对应的user1
print(f"Original users after modification: {all_users}")
print(f"New users list after modification: {new_users_list}")在这个例子中,
new_users_list
all_users
user1
user2
深拷贝虽然提供了完全的独立性,但它并非没有代价。
性能与内存开销:这是最直接的问题。
deepcopy
递归深度限制:Python解释器有默认的递归深度限制(通常是1000)。如果你的数据结构嵌套层级过深,
deepcopy
RecursionError
sys.setrecursionlimit()
循环引用问题:如果你的对象图存在循环引用(例如,对象A引用了B,B又引用了A),
deepcopy
copy
deepcopy
class Node:
def __init__(self, value):
self.value = value
self.next = None
a = Node('A')
b = Node('B')
a.next = b
b.next = a # 形成循环引用
# deepcopy可以正确处理循环引用
deep_copied_a = copy.deepcopy(a)
print(f"Original a.next.next is a: {a.next.next is a}") # True
print(f"Deep copied a.next.next is deep_copied_a: {deep_copied_a.next.next is deep_copied_a}") # True
print(f"Original a is deep_copied_a: {a is deep_copied_a}") # False尽管
deepcopy
不可拷贝的对象:并非所有对象都能被深拷贝。有些对象(如文件句柄、网络连接、线程锁、数据库连接等)是系统资源或外部资源的引用,它们不能被简单地复制。尝试深拷贝这些对象可能会引发
TypeError
AttributeError
__copy__
__deepcopy__
copy
import threading
class MyResource:
def __init__(self, name):
self.name = name
self.lock = threading.Lock() # 线程锁通常不应被深拷贝
def __deepcopy__(self, memo):
# 这是一个简单的示例,实际情况可能更复杂
# 我们可能希望锁是新的,或者根本不拷贝它
new_instance = MyResource(self.name)
# new_instance.lock = threading.Lock() # 创建一个新的锁
# memo[id(self)] = new_instance # 记录已拷贝的对象以处理循环引用
return new_instance
# 尝试深拷贝一个包含线程锁的对象
resource = MyResource("data")
try:
# 默认的deepcopy可能会尝试复制lock对象,这通常是无效的
# 如果没有__deepcopy__,可能会出错或行为异常
deep_copied_resource = copy.deepcopy(resource)
print(f"Deep copied resource name: {deep_copied_resource.name}")
print(f"Original lock ID: {id(resource.lock)}, Deep copied lock ID: {id(deep_copied_resource.lock)}")
except TypeError as e:
print(f"Error during deepcopy: {e}")因此,在使用深拷贝前,我们必须仔细评估其必要性、潜在的性能影响,以及对象图中是否存在不可拷贝的元素。
理解Python的拷贝机制,很大程度上就是理解其对象引用和可变性。避免陷阱,关键在于有意识地思考数据流和对象生命周期。
1. 明确对象的“可变性”:这是理解拷贝行为的基石。Python中的对象分为可变(Mutable)和不可变(Immutable)两种。
2. 区分“赋值”、“浅拷贝”和“深拷贝”的语义:
a = b
a
b
a = b.copy()
a = copy.copy(b)
a
b
a = copy.deepcopy(b)
a
3. 使用 id()
is
id(obj)
id(obj1) == id(obj2)
is
obj1 is obj2
==
list1 = [1, [2, 3]]
list2 = list1 # 赋值
list3 = list1[:] # 浅拷贝
list4 = copy.deepcopy(list1) # 深拷贝
print(f"list1 is list2: {list1 is list2}") # True
print(f"list1 is list3: {list1 is list3}") # False
print(f"list1[1] is list2[1]: {list1[1] is list2[1]}") # True
print(f"list1[1] is list3[1]: {list1[1] is list3[1]}") # True (浅拷贝的内部元素是共享的)
print(f"list1[1] is list4[1]: {list1[1] is list4[1]}") # False (深拷贝的内部元素是独立的)4. 谨慎处理自定义类的拷贝:如果你创建了自己的类,并且它包含可变属性,或者它的实例需要被拷贝,那么你需要考虑如何实现
__copy__
__deepcopy__
__copy__(self)
copy.copy(obj)
__deepcopy__(self, memo)
copy.deepcopy(obj)
memo
copy.deepcopy()
memo
class MyCustomObject:
def __init__(self, data, nested_list):
self.data = data
self.nested_list = nested_list
def __copy__(self):
# 实现浅拷贝:创建一个新实例,浅拷贝属性
cls = self.__class__
new_obj = cls(self.data, self.nested_list) # 注意这里nested_list是直接引用
return new_obj
def __deepcopy__(self, memo):
# 实现深拷贝:创建一个新实例,深拷贝属性
cls = self.__class__
new_obj = cls.__new__(cls) # 创建一个空实例
memo[id(self)] = new_obj # 记录已拷贝的对象,处理循环引用
for k, v in self.__dict__.items():
setattr(new_obj, k, copy.deepcopy(v, memo)) # 递归深拷贝每个属性
return new_obj
obj1 = MyCustomObject(1, [10, 20])
obj_shallow_copy = copy.copy(obj1)
obj_deep_copy = copy.deepcopy(obj1)
obj_shallow_copy.nested_list.append(30)
print(f"Original obj1.nested_list: {obj1.nested_list}") # [10, 20, 30]
obj_deep_copy.nested_list.append(40)
print(f"Original obj1.nested_list: {obj1.nested_list}") # 仍然是 [10, 20, 30]正确实现这些特殊方法,能让你的自定义类在拷贝操作中表现得符合预期。
5. 优先考虑不可变数据结构:在某些情况下,如果你的数据不需要被修改,或者你总是希望在修改时创建新版本,那么使用不可变数据结构(如元
以上就是Python 中的浅拷贝与深拷贝:区别与应用场景的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号