概念与区别
- 浅拷贝通常只复制对象本身。只拷贝原数据的首地址,通过首地址读取内容。
- 深拷贝,新开一片内存把原数据都拷贝过来了,所有数据都是独立的新实例。
浅拷贝:copy.copy()
深拷贝:copy.deepcopy()
假设目前有一组数据,结构有两层,测试结果如下
内层数据类型 | 外层数据类型 | 数据的地址是否改变 |
可变 | 可变 | 外变 内不变 |
不可变 | 可变 | 外变 内不变 |
不可变 | 不可变 | 外不变 内不变 |
可变 | 不可变 | 外不变 内不变 |
内层数据类型 | 外层数据类型 | 数据的地址是否改变 |
可变 | 可变 | 外变 内变 |
不可变 | 可变 | 外变 内不变 |
不可变 | 不可变 | 外不变 内不变 |
可变 | 不可变 | 外变 内变 |
小结:
对于浅拷贝,不管内层数据类型,浅拷贝都不会改变内层地址;外层数据地址随原数据外层数据类型改变
对于深拷贝,内层数据为可变,深拷贝后内外层地址都会改变;内层数据类型为不可变,深拷贝后内层不变,外层随原数据类型改变
深拷贝可能遇到的问题
深拷贝可能会遇到两个问题:一是一个对象如果直接或间接的引用了自身,会导致无休止的递归拷贝;二是深拷贝可能对原本设计为多个对象共享的数据也进行拷贝。
列表、字典实现深拷贝
列表、字典如何实现拷贝操作以及如何通过序列化和反序列的方式实现深拷贝
deepcopy
函数的本质其实就是对象的一次序列化和一次返回序列化
import pickle
author1 = {"1": "1", "2": "2", "3": "3", "4": [5, 6]}
# pickle序列化
pickle_str = pickle.dumps(author1)
print("pickle=>\n", pickle_str)
# pickle字符串反序列化
author3 = pickle.loads(pickle_str)
print("\n",
author1, "\n",
author3, "\n",
)
设计模式中的原型模式
列表的切片操作[:]
相当于实现了列表对象的浅拷贝,而字典的copy
方法可以实现字典对象的浅拷贝。对象拷贝其实是更为快捷的创建对象的方式。在Python中,通过构造器创建对象属于两阶段构造,首先是分配内存空间,然后是初始化。在创建对象时,我们也可以基于“原型”对象来创建新对象,通过对原型对象的拷贝(复制内存)就完成了对象的创建和初始化,这种做法更加高效,这也就是设计模式中的原型模式。在Python中,我们可以通过元类的方式来实现原型模式,代码如下所示。
import copy
class PrototypeMeta(type):
"""实现原型模式的元类"""
def __init__(cls, *args, **kwargs):
super().__init__(*args, **kwargs)
# 为对象绑定clone方法来实现对象拷贝
cls.clone = lambda self, is_deep=True: \
copy.deepcopy(self) if is_deep else copy.copy(self)
class Person(metaclass=PrototypeMeta):
pass
p1 = Person()
p2 = p1.clone() # 深拷贝
p3 = p1.clone(is_deep=False) # 浅拷贝