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

问题描述:

我刚刚回顾了一些为Python创建C扩展模块的代码,这些扩展模块没有包含足够的错误检查。在大多数情况下这很容易,但是对于module-init函数我不确定。如果PyModule_Add *函数失败,模块init中的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是否失败。据it's documentation可以失败:

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

好吧,也许它似乎是矫枉过正打破模块初始化,以防万一类型无法添加。但即使万一他们不想完全中止创建模块:它应该泄露typelist[i]的引用,对吗?

许多内置的CPython C模块不会在模块初始化函数中进行彻底的错误检查和处理(这可能是我正在修复的C扩展中没有它们的原因),它们通常很严格处理这些问题和潜在的泄漏。所以我的问题基本上是:在模块初始化函数中错误检查是否重要,尤其是当涉及到PyModule_Add*函数(如PyModule_AddObject)时?或者它们可以像CPython在很多地方一样被省略?

+0

我认为这太基于观点,真的需要回答。但有些想法...... 1)出现意外错误时的引用计数并不重要 - 你失去了一个对象,它只发生一次,而且程序可能会中止。 2)“PyModule_AddObject”的大多数失败模式将总是发生(即你不会传递它的模块)或永远不会发生。一旦你知道你的模块工作,它可能是相当安全的不检查。 – DavidW

+0

@DavidW如果您认为这是无法回答的,因为它是基于意见的,所以您可以随意投票结束。但你的想法是有道理的。唯一的(不可预知的)失败原因可能是MemoryError(char-> unicode),无论如何在模块导入时解决这个问题是没有意义的。可以有意义地发布它作为答案(至少如果你不认为它应该被关闭:)) – MSeifert

我通常赞成在使用Python的C API时进行严格的错误检查 - 人们经常编写长时间的多步骤函数,不检查任何错误,然后在神秘失败时动作困惑。在这种情况下(模块初始化),你可以证明有错误检查略微宽松:

主要原因是这些函数只会真的失败,因为你的C代码中的错误,他们会做到这一点重复 - 这几乎是不可能的他们将无法预料地在一个不知情的用户身上失败。以PyModule_AddObject为例,它可能会失败,因为:

  • 传递的第一个参数是不是一个模块(!你的错误)
  • 传递的对象是NULL(你应该检查这更早)
  • 该模块没有__dict__(我不知道这是怎么发生的,但是我无法看到它偶然发生到您刚创建的模块)
  • PyDict_SetItemString失败(很可能是由于PyUnicode_FromString失败)。

正如您在评论中指出的那样,后者可能是由MemoryError(可能在任何时候发生且不可预知)造成的。但是,如果您从分配〜10个字符的字符串中获得MemoryError,那么Python解释器不太可能持续更长的时间。所以我想我的结论是“如果你的模块似乎在工作,你可能不需要这个错误检查,但如果事情出错了,那么它对于找出哪里是有用的”。有一两件事我想补充的是一个错误,最后检查你模块返回右前:

if (PyErr_Occurred()) return NULL; 
/* or */ 
if (PyErr_Occurred()) { 
    /* print a warning? */ 
    PyErr_Clear(); 
    return m; 
} 

这样做的原因是,Python可以表现得很奇怪,如果错误指示器设置,但你不回NULL (你会在奇怪的时候提出异常,这是没有意义的)。因此,快速的最终检查具有一定的价值。


对于当模块初始化失败处理参考值:这显然是“最好”把事情做对,但我认为你可以证明它跳过。这是运行一次的代码(所以你不能通过重复丢失少量来损失大量内存)。如果发生错误,最可能的选择是程序中止(所有内存都被恢复)。即使你不放弃,泄漏的大小可能会非常小(约100字节,实际)。