【问题标题】:Does a slicing operation give me a deep or shallow copy?切片操作会给我一个深拷贝还是浅拷贝?
【发布时间】:2013-10-04 19:26:54
【问题描述】:

official Python docs 说在 Python 中使用切片运算符和赋值会生成切片列表的浅表副本。

但是当我写代码例如:

o = [1, 2, 4, 5]
p = o[:]

当我写的时候:

id(o)
id(p)

我得到不同的 id,并且附加一个列表不会反映在另一个列表中。是不是在创建深层副本,还是我哪里出错了?

【问题讨论】:

    标签: python list copy deep-copy


    【解决方案1】:

    您正在创建一个 副本,因为嵌套值不会被复制,而只是被引用。 deep 副本也会创建列表引用的值的副本。

    演示:

    >>> lst = [{}]
    >>> lst_copy = lst[:]
    >>> lst_copy[0]['foo'] = 'bar'
    >>> lst_copy.append(42)
    >>> lst
    [{'foo': 'bar'}]
    >>> id(lst) == id(lst_copy)
    False
    >>> id(lst[0]) == id(lst_copy[0])
    True
    

    这里没有复制嵌套字典;它仅被两个列表引用。新元素42 未共享。

    请记住,Python 中的一切都是对象,名称和列表元素只是对这些对象的引用。列表的副本会创建一个新的外部列表,但新列表仅接收对完全相同的对象的引用。

    适当的深拷贝会递归地创建列表中包含的每个对象的新副本:

    >>> from copy import deepcopy
    >>> lst_deepcopy = deepcopy(lst)
    >>> id(lst_deepcopy[0]) == id(lst[0])
    False
    

    【讨论】:

    • 那为什么我得到不同的ID
    • @user2528042:因为外部列表对象不一样。
    • @user2528042 因为原来的列表被复制到一个新对象。只是其中的所有元素都不会被复制,因此如果列表包含一个可变对象(整数不是可变的),则更改该对象将在原始列表和复制列表中更改它,因为两者都有对同一对象的引用的副本。
    • @user2528042:这是一个浅拷贝并不意味着id(o) == id(p),而是意味着[id(x) for x in o] == [id(x) for x in p]。看出区别了吗?
    • @Davos: lst_copy[0]['foo'] = 'bar' 不会改变list_copy,它只会改变lst_copy[0] 引用的字典对象。在 Python everything is basically a reference 中,包括列表索引。列表的浅表副本创建引用的副本,并且不遵循引用来复制被引用的对象。
    【解决方案2】:

    您应该知道,使用isid 的测试可能会误导您是否使用不可变对象和interned 对象(例如包含不可变对象的字符串、整数和元组)来制作真实副本。

    考虑一个易于理解的实习字符串示例:

    >>> l1=['one']
    >>> l2=['one']
    >>> l1 is l2
    False
    >>> l1[0] is l2[0]
    True
    

    现在制作l1 的浅拷贝并测试不可变字符串:

    >>> l3=l1[:]
    >>> l3 is l1
    False
    >>> l3[0] is l1[0]
    True
    

    现在复制l1[0]所包含的字符串:

    >>> s1=l1[0][:]
    >>> s1
    'one'
    >>> s1 is l1[0] is l2[0] is l3[0]
    True               # they are all the same object
    

    尝试深度复制,每个元素都应该被复制:

    >>> from copy import deepcopy
    >>> l4=deepcopy(l1)
    >>> l4[0] is l1[0]
    True
    

    在每种情况下,字符串 'one' 都被嵌入到 Python 的不可变字符串的内部缓存中,is 将显示它们是相同的(它们具有相同的 id)。它的实现和版本取决于什么被实习和什么时候实习,所以你不能依赖它。它可以是显着的内存和性能增强。

    你可以强制一个不会立即被实习的例子:

    >>> s2=''.join(c for c in 'one')
    >>> s2==l1[0]
    True
    >>> s2 is l1[0]
    False
    

    然后您可以使用 Python intern function 使该字符串引用缓存的对象(如果找到):

    >>> l1[0] is s2
    False
    >>> s2=intern(s2)
    >>> l1[0] is s2
    True
    

    同样适用于不可变元组:

    >>> t1=('one','two')
    >>> t2=t1[:]
    >>> t1 is t2
    True
    >>> t3=deepcopy(t1)
    >>> t3 is t2 is t1
    True
    

    并且不可变的可变列表(如整数)可以将列表成员嵌入:

    >>> li1=[1,2,3]
    >>> li2=deepcopy(li1)
    >>> li2 == li1
    True
    >>> li2 is li1
    False
    >>> li1[0] is li2[0]
    True
    

    因此,您可以使用您知道会复制某些内容的 python 操作,但最终结果是对内部不可变对象的另一个引用。 is 测试只是对正在制作的副本的决定性测试,如果项目是可变的。

    【讨论】:

    • 在 Python 3 上,intern 已移至 sys 模块,因此您需要执行 import sys; s=sys.intern(s)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-08-23
    • 1970-01-01
    • 2012-01-30
    • 2011-10-13
    • 1970-01-01
    • 1970-01-01
    • 2013-03-09
    相关资源
    最近更新 更多