Python中的浅拷贝和深拷贝是对于对象的拷贝而言的。浅拷贝仅拷贝对象的顶层引用,而深拷贝会将对象及其内部所有的引用都进行拷贝。
具体来说,浅拷贝会创建一个新的对象,但是该对象的某些子对象仍然是源对象的引用。而深拷贝会创建一个全新的对象,包括其内部的所有子对象,因此源对象和拷贝对象完全独立。
以下是一个简单的示例代码,演示了浅拷贝和深拷贝的区别:
import copy
# 定义一个嵌套列表
list1 = [[1, 2, 3], [4, 5, 6]]
# 浅拷贝
list2 = copy.copy(list1)
# 修改子对象
list2[0][1] = 9
# 输出结果
print("浅拷贝:")
print("源对象:", list1)
print("拷贝对象:", list2)
# 深拷贝
list3 = copy.deepcopy(list1)
# 修改子对象
list3[0][1] = 8
# 输出结果
print("深拷贝:")
print("源对象:", list1)
print("拷贝对象:", list3)
输出结果如下:
浅拷贝:
源对象: [[1, 9, 3], [4, 5, 6]]
拷贝对象: [[1, 9, 3], [4, 5, 6]]
深拷贝:
源对象: [[1, 9, 3], [4, 5, 6]]
拷贝对象: [[1, 8, 3], [4, 5, 6]]
从输出结果可以看出,对于浅拷贝来说,修改拷贝对象的子对象会同时修改源对象的子对象。而对于深拷贝来说,源对象和拷贝对象互不影响,修改拷贝对象的子对象不会影响源对象的子对象。
除了列表,Python中的其他内置类型(如字典、集合等)也可以进行浅拷贝和深拷贝。对于用户自定义的类,也可以使用 copy 模块中的 copy() 和 deepcopy() 方法进行浅拷贝和深拷贝。
接下来,笔者来演示一下如何使用 copy() 方法进行浅拷贝:
import copy
# 定义一个类
class MyClass:
def __init__(self, x):
self.x = x
# 创建一个对象
obj1 = MyClass(1)
# 浅拷贝
obj2 = copy.copy(obj1)
# 修改拷贝对象的属性
obj2.x = 2
# 输出结果
print("浅拷贝:")
print("源对象:", obj1.x)
print("拷贝对象:", obj2.x)
输出结果如下:
浅拷贝:
源对象: 1
拷贝对象: 2
从输出结果可以看出,对于浅拷贝来说,修改拷贝对象的属性会同时修改源对象的属性。
如果要进行深拷贝,则可以使用 deepcopy() 方法。下面是一个示例代码,演示如何使用 deepcopy() 方法进行深拷贝:
import copy
# 定义一个类
class MyClass:
def __init__(self, x):
self.x = x
# 创建一个对象
obj1 = MyClass([1, 2, 3])
# 深拷贝
obj2 = copy.deepcopy(obj1)
# 修改拷贝对象的属性
obj2.x[1] = 4
# 输出结果
print("深拷贝:")
print("源对象:", obj1.x)
print("拷贝对象:", obj2.x)
输出结果如下:
深拷贝:
源对象: [1, 2, 3]
拷贝对象: [1, 4, 3]
从输出结果可以看出,对于深拷贝来说,修改拷贝对象的属性不会影响源对象的属性。
需要注意的是,对于循环引用的对象,使用 copy() 方法进行浅拷贝会出现无限递归的情况,从而导致程序崩溃。而使用 deepcopy() 方法可以避免这种情况的发生。
为了加深大家的理解,笔者再用一段代码来演示循环引用对象的浅拷贝会出现无限递归的情况:
import copy
# 定义一个循环引用的对象
lst = [1, 2]
lst.append(lst)
# 浅拷贝
lst2 = copy.copy(lst) # 会导致无限递归,程序会崩溃
由于 lst 中包含了对自身的引用,所以进行浅拷贝的时候会出现无限递归的情况,从而导致程序崩溃。
如果要处理循环引用的对象,可以使用 copy 模块中的 copyreg 模块来注册一个处理循环引用的回调函数。
我们可以用如下的方式来处理循环引用的对象:
import copy
import copyreg
# 定义一个循环引用的对象
lst = [1, 2]
lst.append(lst)
# 定义一个处理循环引用的函数
def copy_lst(lst):
return lst
# 注册回调函数
copyreg.pickle(list, copy_lst)
# 浅拷贝
lst2 = copy.copy(lst)
# 输出结果
print("源对象:", lst)
print("拷贝对象:", lst2)
输出结果如下:
源对象: [1, 2, [...]]
拷贝对象: [1, 2, [...]]
从输出结果可以看出,通过注册回调函数,我们可以处理循环引用的对象,从而避免了程序崩溃的情况。