Python的copy.deepcopy()在无警告、异常或错误的情况下失败
这个问题和我昨天发的另一个问题有关,不过它的内容更广泛一些。
因为提到的那个线程,我一直在尝试弄清楚哪些对象可以被复制、序列化(也就是保存到文件中)以及哪些对象不能。
在这个过程中,我遇到了一个难题:
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 个回答
复制和序列化(pickle)会尽量完成它们的工作,但在Python中,最终结果的责任还是在程序员自己身上。
按照难度从简单到复杂排列:
最简单的可以安全复制或序列化的对象基本上是一些基本类型,比如字符串、列表、字典、数字等等。
接下来是一些明确支持特殊方法的对象(比如
__copy__
和__deepcopy__
)。然后是那些复制时会尽力而为并且成功的对象。这些简单对象只包含对前面提到的对象的引用。
最后是那些复制起来真的不安全的对象:
- 有很多循环引用的大型对象图
- 正在被其他线程使用和修改的对象
- 有外部资源的对象:比如文件、连接等
- 包装或代理C语言库的对象
你说“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。