Python C API:通过引用传递参数调用Python函数

1 投票
1 回答
2952 浏览
提问于 2025-04-16 07:58

我正在用C语言写一个Python模块,我需要让它调用一个Python函数,并且其中一个参数是通过“引用”传递的。最终的结果应该是,Python函数对这个参数的修改能够反映到原来的C变量上。

int      *resume;      // this int is actually passed in to this body of code
PyObject *resumeInt;   // which was originally a C callback func for libnids, but
PyObject *ret;         // I removed/rewrote most of this code for clarity

resumeInt = Py_BuildValue("i",-1);
ret = PyObject_CallFunction(tcpResumeFunc, "(O)", resumeInt);    

*resume = PyInt_AsLong(resumeInt);
Py_DECREF(ret);
Py_DECREF(resumeInt);

为了测试,我让tcpResumeFunc这个Python函数把传入的整数改成5。然而,当我在这段代码的最后打印出*resume时,它仍然保持最初的-1值。我知道我对API的工作原理有些误解。有什么建议吗?

1 个回答

4

我觉得你对Python中变量的工作方式有些误解。在Python中,变量并不是直接包含值,而是指向这些值。就像在Java中一样,不过Python没有“基本类型”(连int都没有)。给变量赋值并不会覆盖旧的值,而是让这个变量指向新的值,同时不再指向旧的值(如果没有其他地方再指向旧值,它可能会被垃圾回收)。

那么为什么不直接返回(并使用)一个值呢?因为Python的函数总是会返回一些东西,即使只是隐式返回None


编辑:通过一个例子来说明,因为评论回复太长了。

从C语言中,我们调用PyObject_CallFunction,这个函数会把一个PyObject*传递给Python函数。在Python中,函数的参数(我们称它为spam)现在指向了那个PyObject。

当我们在函数中写spam += 6时,这其实和spam = spam + 6是一样的。字节码解释器会获取表示值6的PyObject,运行一个特殊的字节码来检查这两个对象所代表的值,把它们相加,并创建一个新的PyObject(或者从缓存中获取一个)来表示这个和。

然后spam会重新指向这个新对象;但是spam这个变量引用是在Python这边的内存中,它和C这边的PyObject*并不是同一个东西。

因此,resumeInt不会指向新创建或获取的PyObject。

它的行为实际上和从Python调用Python函数时是完全一样的。试试看:

def fry(spam):
  spam += 1

eggs = 3
fry(eggs)
eggs # still 3!

之所以会这样,是因为重新绑定spam并不会影响eggs,因为eggs是一个独立的变量。我们并不是通过引用传递,而是通过传递一个引用。就像在Java中一样。

撰写回答