我有一个 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。所以问题是:你甚至不能打电话PyTuple_SetItem
除非元组的引用计数为 1。看起来这里的想法是你永远不应该使用PyTuple_SetItem
除非您使用以下命令创建元组之后PyTuple_New()
。我想这是有道理的,因为毕竟元组应该是不可变的,所以这个限制有助于让你的 C 代码更符合 Python 类型系统的抽象。
但是,我在任何地方都找不到此限制的记录。相关文档似乎是here http://docs.python.org/c-api/intro.html#reference-count-details and here http://docs.python.org/py3k/c-api/tuple.html?highlight=pytuple_getitem#PyTuple_SetItem,两者都没有指定此限制。文档基本上说,当你打电话时PyTuple_New(X)
,元组中的所有项目都被初始化为NULL
。自从NULL
不是有效的 Python 值,扩展模块程序员需要确保在将元组返回到解释器之前,元组中的所有槽都已填充正确的 Python 值。但它并没有在任何地方说明当 Tuple 对象的引用计数为 1 时必须执行此操作。
所以现在的问题是,我基本上已经把自己编码到了一个角落,因为我没有意识到这个(无证?)限制PyTuple_SetItem
。我的代码的结构方式使得将项目插入元组非常不方便,直到after元组本身已成为另一个对象的属性。因此,当需要填充元组中的项目时,元组已经具有更高的引用计数。
我可能必须重组我的代码,但我正在认真考虑暂时将 Tuple 上的引用计数设置为 1,插入项目,然后恢复原始引用计数。当然,我知道这是一个可怕的黑客行为,而不是任何永久的解决方案。无论如何,我想知道关于元组引用计数的要求是否是有记录的任何地方。它只是 CPython 的实现细节,还是 API 用户可以依赖的预期行为?