CPython 的 PyLong_FromLong(0L) 永远不会失败?

CPython's PyLong_FromLong(0L) never fails?

这段代码来自Python C-API reference:

item = PyLong_FromLong(0L);
if (item == NULL)
    goto error;

假设解释器是CPython,Python0对象的内存是, so what could go wrong there? Reading the source code for PyLong_FromLong, for small-integer values it would immediately return get_small_int((sdigit)0L). The get_small_int function is really very simple:

static PyObject *
get_small_int(sdigit ival)
{
    assert(IS_SMALL_INT(ival));
    PyThreadState *tstate = _PyThreadState_GET();
    PyObject *v = (PyObject*)tstate->interp->small_ints[ival + NSMALLNEGINTS];
    Py_INCREF(v);
    return v;
}

第一行的断言不会失败,因为PyLong_FromLong已经验证过了。 _PyThreadState_GET() 是一个宏,根据其定义旁边的 commentis unsafe: it does not check for error and it can return NULL. 这可能看起来像是失败的根源,但 请注意 tstate->interp 是正常访问 ,如果宏返回 NULL,这将导致解释器出现段错误。之后,对 Python 0 对象的引用被 Py_INCREF 返回给 PyLong_FromLong(0L).

的原始调用者

我是否正确理解了 CPython 的 PyLong_FromLong 不会因小整数参数而失败,还是我错过了什么?另外,为了完整起见,用 C 编写的扩展模块是否可以从其他解释器中使用,或者在编写它们时我可以假设它们将处理 CPython?

是的,你完全正确,这里永远不会有任何错误,小整数是预先分配的,所以 PyLong_FromLong(0L); 永远不会失败。

但为什么还要测试 item 是否为 NULL?如我所见,这只是一个部分:

  • 新建一个 PyLongObject;
  • 然后测试是否成功

PyLong_FromLong 通过缓存小整数进行了一些优化,但它仍然 returns 大部分和合乎逻辑地是一个新对象,尽管 0L 没有。作为函数调用者,我们不应该依赖内部缓存策略。所以留下一些代码来检查会更有意义也更安全。