不使用深拷贝函数,实现嵌套列表的“深度拷贝”。

5

我想复制嵌套列表 a,但不知道如何在不使用 copy.deepcopy 函数的情况下实现。

a = [[1, 2], [3, 4]]

我使用了:

b = a[:]

并且

b = a[:][:]

但它们最终都变成了浅拷贝。有什么提示吗?

2
你的第二次尝试和第一次一样。 - Ignacio Vazquez-Abrams
1
@Serdalis 这是一个大部分的浅拷贝。想象一下列表元素不是整数。b=a[:]确实创建了一个新的列表,但它的元素是对a中原始项的引用。例如: a=[[]];b=a[:];b[0].append(1);print a生成[[1]],因为b中的第一个元素与a中的第一个元素是相同的对象。深度复制将导致两个不同的对象 - 你可以自己试试。 - Kirk Strauser
5
递归可以解决你的问题。 - avasal
1
@xiaohan2012 a[:] 返回一个包含相同元素的新列表。因为它是一个求值为列表的表达式,所以支持所有列表操作,包括切片。因此,a[:][:] 返回一个包含相同元素的新列表,然后对该列表进行一次新的复制,使其具有相同的元素(仍然与第一个列表相同)。然后 a[:][:][:] 只是再次进行复制。无论您多少次使用相同元素创建新列表的副本... 它仍然具有相同的元素。 - Ben
2
基本上,进行深度复制(具有内容的完全通用性)是一项非常复杂的操作。即使是 copy.deepcopy 在许多情况下也不能立即正确地完成它(您可以提供自己的 copydeepcopy 方法来解决这个问题)。如果您要自己实现此操作,则只能通过进行严格限制的假设来获得简单的结果,例如“我只复制包含内置类型的列表”。 - Ben
显示剩余8条评论
5个回答

9

我编写了一个模拟copy.deepcopy的实现:

def deepcopy(obj):
    if isinstance(obj, dict):
        return {deepcopy(key): deepcopy(value) for key, value in obj.items()}
    if hasattr(obj, '__iter__'):
        return type(obj)(deepcopy(item) for item in obj)
    return obj

策略:遍历传入对象的每个元素,递归进入也是可迭代的元素,并创建它们相同类型的新对象。
我完全不声称这是全面的或没有错误[1](不要传入引用自身的对象!),但应该可以帮助您入门。
[1] 真的!这里的重点是演示,而不是覆盖每种可能性。copy.deepcopy 的源代码有50行长,并且它不能处理所有情况。

这个程序会在 Python 3 字符串上进入无限循环(它们有 __iter__ 方法)。此外,这个解决方案性能较差,并假设类始终可以在构造函数中处理其对象。 - JBernardo
@xiaohan2012 - 很高兴能帮忙! @Raymond - 谢谢!关于Python,我最喜欢的事情是它第一次运行就成功了。 @JBernardo - 正如我所说,那不是一个全面的解决方案。我只是想给他一个基础来进行工作。它有很多问题,比如不能处理 type(obj).__init__ 不能复制 obj 的情况。但是,考虑到只有6行代码可以证明这个观点,我可以接受这一点。 :-) - Kirk Strauser
type(obj)(obj) 的适用范围仅限于内置类型,对于几乎所有的类实例都无效。而且它也不会复制任何不可变类型(似乎 str(s) 直接返回 s 是合理的,因为字符串是不可变的)。但由于唯一既可变又是内置类型的类型是可迭代类型,所以最好只需 return obj,因为 return type(obj)(obj) 声称复制 obj,但实际上它几乎从不这样做。 - Ben
@Ben:我经常编写能够复制自己的类。如果您不需要,那没关系,但这在我的代码库中实际上可以发挥作用。但再次强调:这只是为了演示目的。 - Kirk Strauser
@Kirk,这远非正常。请查看标准库中的所有类。我知道我不能代表所有Python程序员,但我从未见过一个构造函数参数被毁得如此彻底的类,以至于后面的参数都是可选的,而第一个参数可以是任何它通常是的东西,或者是这个类的实例(这会触发复制)。为了做到这一点所需的类型检查经常被认为是“不符合Python风格”的,因为它干扰了鸭子类型。我认为这行代码不适合展示给新的Python程序员。 - Ben

7
如果只有一个级别,您可以使用“LC”。
b = [x[:] for x in a]

那么多重嵌套的列表怎么办? - xiaohan2012
4
切换到递归解决方案,或者忍痛使用 deepcopy() - Ignacio Vazquez-Abrams

2

这是一个完全的欺骗 - 但适用于“基本类型”列表 - 列表,字典,字符串,数字:

def cheat_copy(nested_content):
  return eval(repr(nested_content))

这涉及到强大的安全问题,速度也不会特别快。使用json.dumps和loads将更加安全。


如果您不能信任嵌套内容,那么这显然是危险的。但这种方法也很吸引人,因为非常易读和有效。 - Kaligule
Eval在大多数情况下都是非常危险的。相反,您可以选择使用json dumps / json read来获得更安全的版本。 - Danny Staple

1

我找到了一种使用递归的方法来实现它。

def deep_copy(nested_content):
    if not isinstance(nested_content,list):
        return nested_content
    else:
        holder = []
        for sub_content in nested_content:
            holder.append(deep_copy(sub_content))
        return holder

你的答案和我的不同之处在于,你没有处理除列表以外的容器。如果你寻找 nested_content.__iter__,你可以使用 type(nested_content) 来创建一个相同类型的新对象。此外,你实际上并没有复制你的列表内容 - 你正在构建引用原始项目的新列表。 - Kirk Strauser
是的,我明白了。你的策略对我来说有点复杂,当然比我的更好、更通用。 - xiaohan2012

-1

对于递归版本,您需要跟踪一个辅助列表并每次返回。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接