欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

第8章:对象引用、可变性和垃圾回收-浅复制和深复制

程序员文章站 2024-01-03 17:50:52
...

浅复制

复制列表(或其他可变类型)最简单的办法是使用内置的类型构造方法(还可以用 copy 内置库的 copy() 方法)例如:

l1 = [3, [66, 55, 44], (7, 8, 9)]
l2 = list(l1)

print(l1 == l2)  # True
print(l1 is l2)  # False

第8章:对象引用、可变性和垃圾回收-浅复制和深复制

然而,使用构造方法或 cpoy.copy() 做的都是浅复制(即复制了最外层容器,副本中的元素还是源容器中的元素的引用,见上图)。如果所有的元素都是不可变的,那么这样做没有问题,还能节省内存,但是如果又可变类型的元素,可能会导致意想不到的问题。

例如下面的例子,可以根据注释和内存示意图结合起来一起理解:

# 参考图一,复制列表
l1 = [3, [66, 55, 44], (7, 8, 9)]
l2 = list(l1)

# 参考图二,l1 添加元素 100,l1[1] 移除元素 50,因为 l1[1] 是可变类型引用,
# 所以 l2[1] 和 l1[1] 都指向用一个对象,l1[1] 修改之后 l2 的值也会变化;
l1.append(100)
l1[1].remove(55)
print('l1:', l1)  # l1: [3, [66, 44], (7, 8, 9), 100]
print('l2:', l2)  # l2: [3, [66, 44], (7, 8, 9)]

# 参考图三,l2 添加元素,l2[1] 修改会同步修改 l1,但是注意,l2[2] 因为引用的是不可变类型,
# 所以当执行 l2[2] += (10, 11) 时不会修改源容器,而是会新建一个元组容器,所以 l2[2] 的值改变而 l1[2] 的值不变;
l2[1] += [33, 22]
l2[2] += (10, 11)
print('l1:', l1)  # l1: [3, [66, 44, 33, 22], (7, 8, 9), 100]
print('l2:', l2)  # l2: [3, [66, 44, 33, 22], (7, 8, 9, 10, 11)]

                         图一:                                                              图二:                                                         图三:

第8章:对象引用、可变性和垃圾回收-浅复制和深复制   第8章:对象引用、可变性和垃圾回收-浅复制和深复制     第8章:对象引用、可变性和垃圾回收-浅复制和深复制

深复制 

浅复制没什么问题,但有时我们需要的是深复制(即副本不共享内部对象的引用),我们可以用 copy 模块提供的 deepcopy() 函数为任何对象做深复制。

为了演示 dopy() 和 deepcopy() 的用法,下面示例定义了一个简单的类,用这个类表示运载乘客的校车,在途中会有乘客上下车:

class Bus:

    def __init__(self, passengers=None):
        if passengers is None:
            self.passengers = []
        else:
            self.passengers = list(passengers)

    def pick(self, name):
        self.passengers.append(name)

    def drop(self, name):
        self.passengers.remove(name)


# 创建实例
bus1 = Bus(['A', 'B', 'C', 'D'])
# 浅复制
bus2 = copy.copy(bus1)
# 深复制
bus3 = copy.deepcopy(bus1)

# 变量各自指向不同的对象
print(id(bus1), id(bus2), id(bus3))  # 4461920832 4461921056 4461922176

# 修改 bus1 乘客,发现 bus2 同步发生变化,bus3 没有变化
bus1.drop('B')
print(bus2.passengers)  # ['A', 'C', 'D']
print(bus3.passengers)  # ['A', 'B', 'C', 'D']

# 实际上 bus1 和 bus2 的 passengers 属性都是指向同一个对象,bus 3是单独的一个对象
print(id(bus1.passengers), id(bus2.passengers), id(bus3.passengers))

内存示意图: 

第8章:对象引用、可变性和垃圾回收-浅复制和深复制

此外,有时候深复制可能会太深了,例如对象可能会引用不该复制的外部资源和单例值,我们可以实现特殊方法 __copy____deepcopy__ 来控制 copydeepcopy 的行为。 

上一篇:

下一篇: