默认做浅复制
复制序列最简单的方法是使用内置的类型构造方法。比如:
l1 = [3, [66, 55, 44], (7, 8, 9)]
l2 = list(l1) #创建l1的副本
print(l2 == l1 ) # True: 相等
print( l2 is l1 ) # False: 但是不相同
l1.append(100) #把 100 追加到 l1 中,对 l2 没有影响。
print(l1) #[3, [66, 55, 44], (7, 8, 9), 100]
print(l2) #[3, [66, 55, 44], (7, 8, 9)]
l1[1].remove(55) #把内部列表 l1[1] 中的 55 删除。这对 l2 有影响,因为 l2[1] 绑定的列表与 l1[1] 是同一个。
print(l1) #[3, [66, 44], (7, 8, 9), 100]
print(l2) #[3, [66, 44], (7, 8, 9)]
l2[1] += [33, 22] #对可变的对象来说,+= 运算符就地修改列表
print(l1) #[3, [66, 44, 33, 22], (7, 8, 9), 100]
print(l2) #[3, [66, 44, 33, 22], (7, 8, 9)]
l2[2] += (10, 11) #对元组来说,+= 运算符创建一个新元组
print(l1) #[3, [66, 44, 33, 22], (7, 8, 9), 100]
print(l2) #[3, [66, 44, 33, 22], (7, 8, 9, 10, 11)]
然而,构造方法或者[:]做的是浅复制(即复制了最外层容器,副本中的元素是源容器中元素的引用)。
- 如果所有元素都是不可变的,那么可以节省内存
- 如果有可变的元素,那么就可能导致问题
浅复制容易操作,但是得到的结果可能并不是你想要的
为任意对象做深复制和浅复制
copy模块提供的 deepcopy 和 copy 函数能为任意对象做深复制和浅复制。
class Point :
""" Represents ␣a␣ point ␣in␣2−D␣ space ."""
class Rectangle :
""" Represents ␣a␣ rectangle .
␣␣␣␣ attributes :␣width ,␣ height ,␣ corner .
␣␣␣␣ """
别名会降低程序的可读性,因为一个地方的变动可能对另一个地方造成预料之外的影响。跟踪所有引用同一个对象的变量是非常困难的。
通常用复制对象的方法取代为对象起别名。copy 模块拥有一个叫做 copy 的函数,可以复制任何对象:
p1 = Point()
p1.x = 3.0
p1.y = 4.0
import copy
# p1 和 p2 拥有相同的数据,但是它们并不是同一个 Point 对象。
p2 = copy.copy(p1)
p3 = p1
print(p1) # <__main__.Point object at 0x000002321E3BBD30>
print(p2) #<__main__.Point object at 0x0000029DEC3FAE30>
print(p3) # <__main__.Point object at 0x0000029DEC3FBD30>
print(p1 is p2) #False
print(p1 == p2) #False
print(p1 is p3) #True
print(p1 == p3) #True
==运算符的默认行为和 is 运算符相同;它检查对象的标识 (identity) 是否相同,而非对象的值是否相同。
如果你使用 copy.copy 来复制一个 Rectangle ,你会发现它仅仅复制了 Rectangle 对象,但没有复制嵌套的 Point 对象。
box = Rectangle ()
box.width = 100.0
box.height = 200.0
box.corner = Point ()
box.corner.x = 0.0
box.corner.y = 0.0
box2 = copy.copy(box)
print(box) # <__main__.Rectangle object at 0x0000015F1C97BD60>
print(box2) #<__main__.Rectangle object at 0x0000015F1C97B2B0>
print(box is box2) #False
print(box == box2) #False
print(box.corner is box2.corner) #True
print(box.corner == box2.corner) #True
这个操作叫做浅复制 (shallow copy) ,因为它仅复制了对象以及其包含的引用,但未复制嵌套的对象。
怎么办呢?copy 模块拥有一个叫做 deepcopy 的方法,它不仅可以复制一个对象,还可以复制这个对象所引用的对象,甚至可以复制这个对象所引用的对象 所引用的对象,等等。没错!这个操作叫做深复制 (deep copy) 。
box2 = copy.deepcopy(box)
print(box) # <__main__.Rectangle object at 0x0000015F1C97BD60>
print(box2) #<__main__.Rectangle object at 0x0000015F1C97B2B0>
print(box is box2) #False
print(box == box2) #False
print(box.corner is box2.corner) #False
print(box.corner == box2.corner) #False
循环引用
注意,一般来说,深复制不是件简单的事。如果对象有循环引用,那么这个朴素的算法会进入无限循环。deepcopy 函数会记住已经复制的对象,因此能优雅地处理循环引用
a = [10, 20]
b = [a, 30]
a.append(b)
print(a) #[10, 20, [[...], 30]]
print(b) # [[10, 20, [...]], 30]
from copy import deepcopy
c = deepcopy(a)
print(c) #[10, 20, [[...], 30]]
此外,深复制有时可能太深了。例如,对象可能会引用不该复制的外部资源或单例值。我们可以实现特殊方法 //copy_() 和 //deepcopy_(),控制 copy 和 deepcopy 的行为