我刚刚查看了一些代码,这些代码为Python创建了一个C扩展模块,但没有包含足够的错误检查。在大多数情况下这很容易,但是当涉及到模块初始化函数时,我不确定。
仅仅为了讨论,让我们采取(abriged)module-init function for itertools
(是的,CPython发布的那个):
m = PyModule_Create(&itertoolsmodule);
if (m == NULL)
return NULL;
for (i=0 ; typelist[i] != NULL ; i++) {
if (PyType_Ready(typelist[i]) < 0)
return NULL;
name = strchr(typelist[i]->tp_name, '.');
assert (name != NULL);
Py_INCREF(typelist[i]);
PyModule_AddObject(m, name+1, (PyObject *)typelist[i]);
}
return m;
检查PyModule_Create
是否失败(这是好的),然后检查PyType_Ready
是否失败(这是好的)但在这种情况下它不是Py_DECREF(m)
(这是令人惊讶/困惑)但它完全无法检查PyModule_AddObject
是否失败。根据{{3}},可以失败:
好吧,如果无法添加类型,打破模块初始化似乎有些过分。但即使万一他们不想完全中止创建模块:它应该泄漏将对象添加到模块名称。这是一个便利功能,可以从模块的初始化功能中使用。这窃取了对价值的引用。错误时返回-1,成功时返回0。
typelist[i]
的引用,对吗?
许多内置的CPython C模块都没有在module-init函数中进行彻底的错误检查和处理(这可能就是为什么我正在修复的C扩展还没有它们)并且它们通常非常严格有这些问题和潜在的泄漏。所以我的问题基本上是:错误检查在module-init函数中是否重要,特别是涉及PyModule_Add*
函数(如PyModule_AddObject
)时?或者可以像许多地方的CPython一样省略它们?
答案 0 :(得分:2)
我在使用Python的C API时一般都支持严格的错误检查 - 人们经常编写冗长的多步骤函数,不检查任何错误,然后在行动时感到困惑神秘地失败了。在这种情况下(模块初始化),您可以证明在检查错误时略显松懈:
主要原因是这些功能只会因为C代码中的错误而真正失败,并且它们会重复这样做 - 它们几乎不可能在不知情的用户上无法预测地失败。以PyModule_AddObject
为例,它可能会失败,因为:
NULL
(您应该先检查一下)__dict__
(我不知道这是怎么发生的,但是我无法看到它偶然发生在你所使用的模块上刚刚创建)PyDict_SetItemString
失败(很可能是PyUnicode_FromString
失败造成的)。正如你在评论中指出的那样,后者可能是由MemoryError
引起的(这可能在任何时候发生并且是不可预测的)。但是,当您从分配~10个字符串获得MemoryError
时,Python解释器不太可能设法继续保持更长的时间。
所以我认为我的结论是&#34;如果你的模块似乎正在工作,你可能不需要这个错误检查,但是如果出现问题那么它对于找出&# 34 ;.我可能会添加的一件事是在您返回模块之前对错误进行最终检查:
if (PyErr_Occurred()) return NULL;
/* or */
if (PyErr_Occurred()) {
/* print a warning? */
PyErr_Clear();
return m;
}
这样做的原因是,如果设置了错误指示但是你没有返回NULL
,那么Python会表现得非常奇怪(你会在奇怪的时候得到异常,而不是有道理)。因此,快速的最终检查具有一定的价值。
关于模块初始化失败时的参考处理:显然&#34;最好&#34;做得对,但我认为你可以为跳过它辩护。这是运行一次的代码(因此您不能通过反复丢失少量内容而丢失大量内存)。如果发生错误,则最可能的选项是程序中止(因此恢复所有内存)。即使你不中止,泄漏的大小也可能非常小(约100字节,实际上)。