Python的copy.deepcopy()在无警告、异常或错误的情况下失败

3 投票
2 回答
2691 浏览
提问于 2025-04-18 00:17

这个问题和我昨天发的另一个问题有关,不过它的内容更广泛一些。

因为提到的那个线程,我一直在尝试弄清楚哪些对象可以被复制、序列化(也就是保存到文件中)以及哪些对象不能。

在这个过程中,我遇到了一个难题:

new_obj = copy.deepcopy(my_obj)
function_that_uses_my_new_obj(new_obj)

出现了:

    function_that_uses_my_new_obj(new_obj)

运行时错误:内部的 C++ 对象 (Pyside.QtGui.QWidget) 已经被删除

现在,由于my_obj 确实是一个C++对象,所以我能理解这个错误。而这个特定问题的原因正是另一个线程的主要话题。

但是,当我尝试:

function_that_uses_my_new_obj(copy.deepcopy(my_obj))

我根本没有得到任何反馈。程序正常运行到这一行,停在那里几秒钟,然后执行就停止了,后面的代码没有运行,也没有抛出任何异常、错误或警告,Python 提示符准备好接受任何新命令。

编辑

出于某种原因,使用copy()方法而不是deepcopy(),像这样:

function_that_uses_my_new_obj(copy.copy(my_obj))

结果抛出了相同的异常。所以一定有某个时刻,deepcopy决定停止或者被迫停止,这导致了执行的结束。我不明白的是,为什么没有任何信息提示用户……

2 个回答

3

复制和序列化(pickle)会尽量完成它们的工作,但在Python中,最终结果的责任还是在程序员自己身上。

按照难度从简单到复杂排列:

  1. 最简单的可以安全复制或序列化的对象基本上是一些基本类型,比如字符串、列表、字典、数字等等。

  2. 接下来是一些明确支持特殊方法的对象(比如 __copy____deepcopy__)。

  3. 然后是那些复制时会尽力而为并且成功的对象。这些简单对象只包含对前面提到的对象的引用。

  4. 最后是那些复制起来真的不安全的对象:

    • 有很多循环引用的大型对象图
    • 正在被其他线程使用和修改的对象
    • 有外部资源的对象:比如文件、连接等
    • 包装或代理C语言库的对象
2

你说“my_obj是一个C++对象”这句话明显是错的:其实my_obj是一个Python的包装器,它是“围绕”一个C++对象的。所以像下面这样的代码:

    widget = QtGui.QWidget()
    my_obj = copy.deepcopy(widget)

只会创建一个Python包装器的副本,而不会动到底层的C++对象。这就解释了为什么尝试调用复制后的包装方法会出现RuntimeError(运行时错误)。这个包装器的副本“从来没有”对应的C++对象,所以它的表现就像是已经被删除了一样。

这种情况在“正常”的PySide/PyQt代码中很容易发生。有时候,如果你没有好好保留Python那边的对象引用,Qt会把C++部分删除,这样你就只剩下一个“空”的包装器。在这种情况下,你会看到完全相同的RuntimeError。

撰写回答