PyTuple_SetItem的局限性

时间:2011-05-24 14:16:52

标签: python c tuples python-c-api

我有一个Python扩展模块,它创建一个元组作为另一个对象的属性,并在元组中设置项目。每当我在Python中执行此模块时,我都会收到错误SystemError: bad argument to internal function

在阅读PyTuple的文档并调试我的程序几个小时后,我仍然无法弄清楚到底是怎么回事。通过调试器运行我的程序表明问题发生在Python解释器内的库调用中。所以,最后,我查看了Python源代码,最后我意识到了这个问题。 PyTuple_SetItem函数有一个我不知道的有趣限制,并且无法明确记录。

这是Python源代码中的重要功能(为清晰起见而编辑):

int PyTuple_SetItem(register PyObject *op, register Py_ssize_t i, PyObject *newitem)
{
    .....
    if (!PyTuple_Check(op) || op->ob_refcnt != 1) {
        Py_XDECREF(newitem);
        PyErr_BadInternalCall();
        return -1;
    }
    .....
}

这里重要的一点是条件 op-> ob_refcnt!= 1 。所以这就是问题所在:除非元组的重计数为1,否则你甚至不能调用PyTuple_SetItem。看起来这里的想法是你永远不应该使用PyTuple_SetItem除了之后你使用PyTuple_New()创建一个元组。我认为这是有道理的,因为毕竟Tuples应该是不可变的,所以这个限制有助于保持你的C代码更符合Python类型系统的抽象。

但是,我无法在任何地方找到此限制。相关文档似乎是herehere,两者均未指定此限制。文档基本上说当你调用PyTuple_New(X)时,元组中的所有项都被初始化为NULL。由于NULL不是有效的Python值,因此扩展模块程序员需要确保在将Tuple返回到解释器之前,使用正确的Python值填充元组中的所有插槽。但是,当Tuple对象的引用计数为1时,它没有说任何必须这样做。

所以现在问题是我基本上把自己编成了一个角落,因为我不知道PyTuple_SetItem的这个(未记录的?)限制。我的代码的结构使得将元素插入元组非常不方便,直到之后元组本身成为另一个对象的属性。因此,当需要填写元组中的项目时,元组已经有更高的引用计数。

我可能不得不重构我的代码,但我认真考虑暂时将元组上的引用计数设置为1,插入项目,然后恢复原始引用计数。当然,这是一个可怕的黑客,我知道,而不是任何永久的解决方案。无论如何,我想知道关于元组的引用计数的要求是否记录在任何地方。它只是CPython的实现细节,还是API用户可以依赖的预期行为?

2 个答案:

答案 0 :(得分:7)

我非常确定您可以使用PyTuple_SET_ITEM代替PyTuple_SetItem来解决这些限制。 PyTuple_SET_ITEMtupleobject.h中定义的宏,如下所示:

#define PyTuple_SET_ITEM(op, i, v) (((PyTupleObject*)(op))->ob_item[i] = v

所以,如果你绝对,绝对并且完全确定:

  1. op是一个元组对象
  2. 到目前为止,你还没有在元组中初始化插槽i
  3. 您拥有对v的引用,并且您希望让元组窃取它并
  4. 在致电PyTuple_SET_ITEM
  5. 之前,没有其他Python对象使用元组的机会

    然后我猜你可以安全地使用PyTuple_SET_ITEM

答案 1 :(得分:2)

Python C API 非常未被记录,如果没有提及任何地方,我不会感到惊讶。

当然,一旦某些东西得到了控制,你就永远不应该修改元组;传递你需要放入元组的元素,或者改为使用列表。