我有一个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类型系统的抽象。
但是,我无法在任何地方找到此限制。相关文档似乎是here和here,两者均未指定此限制。文档基本上说当你调用PyTuple_New(X)
时,元组中的所有项都被初始化为NULL
。由于NULL
不是有效的Python值,因此扩展模块程序员需要确保在将Tuple返回到解释器之前,使用正确的Python值填充元组中的所有插槽。但是,当Tuple对象的引用计数为1时,它没有说任何必须这样做。
所以现在问题是我基本上把自己编成了一个角落,因为我不知道PyTuple_SetItem
的这个(未记录的?)限制。我的代码的结构使得将元素插入元组非常不方便,直到之后元组本身成为另一个对象的属性。因此,当需要填写元组中的项目时,元组已经有更高的引用计数。
我可能不得不重构我的代码,但我认真考虑暂时将元组上的引用计数设置为1,插入项目,然后恢复原始引用计数。当然,这是一个可怕的黑客,我知道,而不是任何永久的解决方案。无论如何,我想知道关于元组的引用计数的要求是否记录在任何地方。它只是CPython的实现细节,还是API用户可以依赖的预期行为?
答案 0 :(得分:7)
我非常确定您可以使用PyTuple_SET_ITEM
代替PyTuple_SetItem
来解决这些限制。 PyTuple_SET_ITEM
是tupleobject.h
中定义的宏,如下所示:
#define PyTuple_SET_ITEM(op, i, v) (((PyTupleObject*)(op))->ob_item[i] = v
所以,如果你绝对,绝对并且完全确定:
op
是一个元组对象i
v
的引用,并且您希望让元组窃取它并PyTuple_SET_ITEM
然后我猜你可以安全地使用PyTuple_SET_ITEM
。
答案 1 :(得分:2)
Python C API 非常未被记录,如果没有提及任何地方,我不会感到惊讶。
当然,一旦某些东西得到了控制,你就永远不应该修改元组;传递你需要放入元组的元素,或者改为使用列表。