【问题标题】:Two variables seem to point to the same list, even though they should be unique两个变量似乎指向同一个列表,即使它们应该是唯一的
【发布时间】:2017-01-13 23:11:15
【问题描述】:

我在程序中使用列表,但我不理解以下行为。我已经开始了解可变性以及它如何影响变量分配,但我在这里看不到问题:

class Test:
    def __init__(self, list_n):
        list_a = list_n[:]
        list_b = list_n[:]
        print(list_a is list_b) # Prints False
        print(list_a is list_n) # Prints False
        print(list_b is list_n) # Prints False

        list_a[0][0] = 1
        print(list_a) # Both of these print [[1,0,0][0,0,0][0,0,0]]
        print(list_b)

def main():
    list_n = [[0,0,0],[0,0,0],[0,0,0]]
    test = Test(list_n)      

if __name__ == '__main__': main()

list_alist_b 似乎仍然指向同一个列表,尽管我认为我采取了必要的措施来防止这种情况发生。

【问题讨论】:

标签: python


【解决方案1】:

为什么不使用切片符号来复制我的列表?

您的示例不起作用的原因是您只制作了list_n浅拷贝。列表的浅表副本仅复制“顶级”列表。浅复制列表不会复制子列表。通过在list(list.copy()) 上调用copy() 或使用切片表示法(list[:]) 来制作列表的浅拷贝。

在 C 级别,当对列表元素进行浅拷贝时,指向列表的指针(称为 PyObjects)会从一个列表复制到另一个列表。然而,每个子列表的实际指针不会被复制,因此list_alist_b 都包含指向确切相同子列表的指针。

说白了,您从未复制list_n 中的每个子列表,因此list_alist_b 仍然都包含指向相同子列表的指针。这可以通过创建list_n 的“深拷贝”来解决 - 无论嵌套级别如何,原始列表中每个子列表的副本 - 使用 copy.deepcopy()

>>> from copy import deepcopy
>>> 
>>> list_n = [[0,0,0],[0,0,0],[0,0,0]]
>>> list_a = deepcopy(list_n)
>>> list_b = deepcopy(list_n)
>>> 
>>> list_a[0][0] = 1
>>> list_a
[[1, 0, 0], [0, 0, 0], [0, 0, 0]]
>>> list_b
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
>>> 

什么时候应该使用deepcopy()

使用deepcopy() 的最大缺点之一是“深度复制”浅嵌套列表需要大量时间。

如果您的列表只是浅嵌套(两到三层深),则应该简单地使用 嵌套列表理解 而不是 deepcopy()。使用这种方法会大大提高效率(感谢@jonrsharpe 指出这一点):

>>> list_n = [[0,0,0],[0,0,0],[0,0,0]]
>>> list_a = [x[:] for x in list_n]
>>> list_b = [x[:] for x in list_n]

使用标准库中的timeit模块可以观察到使用这种方法获得的效率:

>>> import timeit
>>> 
>>> setup="from copy import deepcopy; list_n = [[0,0,0],[0,0,0],[0,0,0]]"
>>> timeit.timeit(setup=setup, stmt="deepcopy(list_n)")
24.223977088928223
>>> timeit.timeit(setup=setup, stmt="[x[:] for x in list_n]")
1.2281990051269531
>>>  

但是,如果您的列表更深,应该选择使用deepcopy(),即使它看起来有些庞大。人们通常永远不需要牺牲可读性而不是效率。此外,随着列表理解变得越来越复杂,deepcopy() 的效率开始变小。

为什么deepcopy() 这么慢?

deepcopy() 比大多数其他方法慢得多(感谢@Felix 的提问)的原因是因为deepcopy() 比简单的列表理解所做的工作要多得多。与列表理解不同,deecopy() 必须在任意嵌套的列表上工作,可能有多个嵌套级别。因此,在浅嵌套列表中使用它是非常过大的,并且会导致执行时间慢得多。

要更好地了解 deepcopy() 在幕后的作用,您可以查看函数的源代码,因为它是 open source and available to the public for viewing

【讨论】:

  • 由于它只有一层深且均匀,我怀疑[l[:] for l in list_n] 会比deepcopy 更有效。
  • 嵌套列表组合对于更深的结构会很快变得丑陋,这是真的!不过,它似乎快了两个数量级,所以我想我会提到它。
  • @jonrsharpe 我一点也不介意!事实上,你是完全正确的。使用 timeit 模块,计时 deepcopy() 大约需要 30.5 秒,而您的方法只需要大约 1 秒!感谢您的更新。
  • 为什么deepcopy 这么慢?它实际上并没有比列表理解做更多的工作,不是吗?
  • 如果你为不使用的东西付费,它的设计很糟糕。他们应该在 C 中实现这一点。以这种速度,cPickle/json 甚至比 deepcopy 还要快...
猜你喜欢
  • 1970-01-01
  • 2021-07-01
  • 1970-01-01
  • 2021-03-20
  • 1970-01-01
  • 2022-10-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多