如果PyModule_Add *函数失败,模块init中的C扩展是否会失败?

时间:2017-10-11 12:12:40

标签: python python-c-api cpython python-internals python-c-extension

我刚刚查看了一些代码,这些代码为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一样省略它们?

1 个答案:

答案 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字节,实际上)。