我在程序中使用列表,但我不理解以下行为。我已经开始理解可变性以及它如何影响变量赋值,但我没有看到这里的问题:
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_a
和list_b
似乎仍然指向相同的列表,即使我认为我采取了必要的措施来防止这种情况发生。
答案 0 :(得分:11)
您的示例无效的原因是您只创建了list_n
的浅层副本。列表的浅表副本仅复制"顶级"名单。浅复制列表不会复制子列表。通过copy()
(list
)上的list.copy()
或使用切片表示法(list[:]
)调用列表的浅层副本。
在C级别,当对作为列表的元素进行浅拷贝时,指向列表的指针(称为PyObject
s)正从一个列表复制到另一个列表。但是,不会复制每个子列表的实际指针,因此list_a
和list_b
都包含指向完全相同子列表的指针。
说实话,你从来没有在list_n
中复制每个子列表,因此list_a
和list_b
都包含指向同一个的子列表子列表。这可以通过创建一个" deepcopy" of 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。