Python 如果PyModule_Add*函数失败,C扩展是否应该在module init中失败?

Python 如果PyModule_Add*函数失败,C扩展是否应该在module init中失败?,python,python-c-api,cpython,python-internals,python-c-extension,Python,Python C Api,Cpython,Python Internals,Python C Extension,我刚刚回顾了一些为Python创建C扩展模块的代码,这些代码没有包含足够的错误检查。在大多数情况下,这很容易,但当谈到模块初始化函数时,我不确定 为了便于讨论,让我们看一下(节略)(是的,是由CPython装运的): m=PyModule\u创建(&itertoolsmodule); 如果(m==NULL) 返回NULL; 对于(i=0;类型列表[i]!=NULL;i++){ 如果(PyType_就绪(类型列表[i])tp_名称'); 断言(名称!=NULL); Py_增量(类型列表[i]);

我刚刚回顾了一些为Python创建C扩展模块的代码,这些代码没有包含足够的错误检查。在大多数情况下,这很容易,但当谈到模块初始化函数时,我不确定

为了便于讨论,让我们看一下(节略)(是的,是由CPython装运的):

m=PyModule\u创建(&itertoolsmodule);
如果(m==NULL)
返回NULL;
对于(i=0;类型列表[i]!=NULL;i++){
如果(PyType_就绪(类型列表[i])<0)
返回NULL;
名称=strchr(类型列表[i]>tp_名称');
断言(名称!=NULL);
Py_增量(类型列表[i]);
PyModule_AddObject(m,name+1,(PyObject*)类型列表[i]);
}
返回m;
它确实会检查
PyModule\u Create
是否失败(这是好的),然后检查
PyType\u Ready
是否失败(这是好的),但在这种情况下它不会
Py\u DECREF(m)
(令人惊讶/困惑),但它完全无法检查
PyModule\u AddObject
是否失败。根据它的说法,它可能会失败:

将对象作为名称添加到模块。这是一个方便的功能,可从模块的初始化功能中使用。这窃取了对值的引用。错误时返回-1,成功时返回0

好吧,如果不能添加类型,那么中断模块初始化可能会显得有些过火。但即使他们不想完全中止创建模块:它也应该泄漏对
typelist[i]
的引用,对吗


许多内置的cpythonc模块在moduleinit函数中没有进行彻底的错误检查和处理(这可能就是我正在修复的C扩展也没有的原因),它们通常对此类问题和潜在的泄漏非常严格。因此,我的问题基本上是:错误检查在module init函数中是否很重要,特别是当涉及到
PyModule\u Add*
函数(如
PyModule\u AddObject
)时?或者它们可以像CPython在许多地方所做的那样被忽略?

我通常支持在使用Python的C API时进行严格的错误检查-人们通常编写长的多步骤函数,不检查任何错误,然后在它神秘地失败时表现出困惑。在这种情况下(模块初始化),您可以证明在错误检查方面有点松懈:

主要原因是,这些函数只会因为您的C代码中的错误而真正失败,并且它们会重复执行此操作-对于一个毫无戒心的用户,它们几乎不可能不可预测地失败。例如,它可能会失败,因为:

  • 传递的第一个参数不是模块(您的错误!)
  • 传递的对象为
    NULL
    (您应该在前面检查)
  • 该模块没有
    \uuuu dict\uuuu
    (我不知道这是如何发生的,但我看不到它意外发生在您刚刚创建的模块上)
  • PyDict\u SetItemString
    失败(很可能是由
    PyUnicode\u FromString
    失败引起的)
正如您在评论中指出的,后者可能是由
内存错误
引起的(这可能随时发生,而且不可预测)。但是,当您从分配10个字符串中获得
MemoryError
s时,Python解释器不太可能继续运行更长的时间

所以我认为我的结论是“如果你的模块似乎在工作,你可能不需要这个错误检查,但如果事情出了问题,那么它有助于找出哪里”。我可以添加的一件事是在返回模块之前对错误进行最后检查:

if (PyErr_Occurred()) return NULL;
/* or */
if (PyErr_Occurred()) {
    /* print a warning? */
    PyErr_Clear();
    return m;
}
这样做的原因是,如果设置了错误指示符,但没有返回
NULL
,Python的行为可能会非常奇怪(在没有意义的奇数时间会引发异常)。因此,快速的最终检查具有一定的价值



关于模块初始化失败时的引用处理:显然“最好”正确处理,但我认为您可以跳过它。这是只运行一次的代码(因此,您不能通过反复丢失少量内存来丢失大量内存)。如果发生错误,则最有可能的选择是程序中止(以便恢复所有内存)。即使您不中止,泄漏的大小也可能非常小(实际上约为100字节)。

我认为这是基于观点的,不可能真正负责。但是一些想法。。。1) 发生意外错误时的引用计数其实并不重要——您丢失了一个对象,它只发生一次,而且程序很可能会中止。2)
PyModule_AddObject
的大多数故障模式要么总是发生(即,您没有将模块传递给它),要么永远不会发生。一旦你知道你的模块正常工作,不检查可能是非常安全的。@DavidW如果你认为无法回答,因为它是基于意见的,那么请随意投票关闭。但是你的想法是有道理的。唯一(不可预测的)故障原因可能是
MemoryError
(char->unicode),无论如何,在模块导入时解决这个问题是没有意义的。将其作为答案发布是有意义的(至少如果您认为它不应该关闭:)