python中的浅复制

  • 浅复制
  • 为任意对象做深复制和浅复制


浅复制

复制列表(或多数内置的可变集合)最简单的方式是使用内置的类型构造方法。

如下示例 1

>>> l1 = [3, [55, 44], (7, 8, 9)] 
>>> l2 = list(l1)  #➊ 
>>> l2 
[3, [55, 44], (7, 8, 9)] 
>>> l2 == l1  #➋ 
True 
>>> l2 is l1  #➌ 
False
>>>id(l1),id(l2)
(3024405754952, 3024405755464)

➊ list(l1)创建l1的副本。
➋ 副本与源列表相等。
➌ 但是二者指代不同的对象。

对列表和其他可变序列来说,还能使用简洁的l2 = l1[:]语句创建副本

然而,构造方法或[:]做的是浅复制(即复制了最外层容器,副本汇总的元素是源容易中元素的引用)。如果所有原始都是不可变的,那么就没问题,还能节省内存。但如果有可变的元素,可能会有意想不到的问题。

#示例2:为一个包含另一个列表的列表做浅复制;把这段代码复制粘贴到Python Tutor
网站中,看看动画效果
>>>l1 = [3, [66,55, 44], (7, 8, 9)] 
>>>l2 = list(l1)      # ➊ 
>>>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[1] += [33, 22]  # ➍ 
>>>l2[2] += (10, 11)  # ➎ 
>>>print('l1:', l1) 
l1: [3, [66,44, 59, 33, 50], (7, 8, 9), 100]

>>>print('l2:', l2)
l2 [3, [66,44, 59, 33, 50], (7, 8, 9, 13, 34, 65)]

➊ l2是l1的浅复制副本。此时的状态如图8-3所示。
➋ 把100追加到l1中,对l2没有影响。
➌ 把内部列表l1[1]中的55删除。这对l2有影响,因为l2[1]绑定的列表与l1[1]是同一个。
➍ 对可变的对象来说,如l2[1]引用的列表,+=运算符就地修改列表。这次修改在l1[1]中也有体现,因为它是l2[1]的别名。
➎ 对元组来说,+=运算符创建一个新元组,然后重新绑定给变量l2[2]。这等同于l2[2] = l2[2] + (10, 11)。现在,l1和l2中最后位置上的元组不是同一个对象。

pythonui tkinter复制 python中复制粘贴_元组


执行l2 = list(l1)赋值后的程序状态。l1和l2指代不同的列表,但是二者引用同一个列表[66, 55, 44]和元组(7, 8, 9)。

pythonui tkinter复制 python中复制粘贴_python_02


l1和l2的最终状态:二者依然引用同一个列表对象,现在列表的值是[66, 44, 33, 22],不过l2[2] += (10, 11)创建一个新元组,内容是(7, 8, 9, 10, 11),它与l1[2]引用的元组(7, 8, 9)无关。

浅复制容易操作,但是得到的结果可能并不是你想要的

为任意对象做深复制和浅复制

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

为了演示copy()和deepcopy()的用法,示例3定义了一个简单的类–Bus.

#校车乘客在途中上车和下车
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)
>>> import copy 
>>> bus1 = Bus(['Alice', 'Bill', 'Claire', 'David']) 
>>> bus2 = copy.copy(bus1) 
>>> bus3 = copy.deepcopy(bus1) 
>>> id(bus1), id(bus2), id(bus3) 
(3024405672736, 3024405674808, 3024405620944)  #➊ 
>>> bus1.drop('Bill') 
>>> bus2.passengers 
['Alice', 'Claire', 'David']          #➋ 
>>> id(bus1.passengers), id(bus2.passengers), id(bus3.passengers) 
(3024405312456, 3024405312456, 3024388357448)  #➌ 
>>> bus3.passengers 
['Alice', 'Bill', 'Claire', 'David']  #➍

➊ 使用copy和deepcopy,创建3个不同的Bus实例。
➋ bus1中的’Bill’下车后,bus2中也没有他了。
➌ 审查passengers属性后发现,bus1和bus2共享同一个列表对象,因为bus2是bus1的浅复制副本。
➍ bus3是bus1的深复制副本,因此它的passengers属性指代另一个列表。

一般来说,深复制不是件简单的事。如果对象有循环引用,那么这个朴素的算法会进入无限循环。

deepcopy函数会记住已经复制的对象,因此能优雅地处理循环引用。如下示例3

#示例3:循环引用:b引用a,然后追加到a中;deepcopy会想办法复制a
>>> a = [10, 20] 
>>> b = [a, 30] 
>>> a.append(b) 
>>> a 
[10, 20, [[...], 30]] 
>>> from copy import deepcopy 
>>> c = deepcopy(a) 
>>> c 
[10, 20, [[...], 30]]

此外,深复制有时可能太深了。例如,对象可能会引用不该复制的外部资源或单例值。我们可以实现特殊方法__copy__()和__deepcopy__(),控制copy和deepcopy的行为。