如何使用Python C API在C扩展模块中实现闭包功能?
Python C API 实现闭包的完整方案
要实现和Python原生闭包完全一致的效果,我们可以利用Python C API中PyCFunction可绑定自定义上下文的特性,将外部捕获的变量作为返回函数的self上下文存储,以下是完整实现步骤:
1. C扩展核心代码(mymodule.c)
#include <Python.h> // 闭包内add函数的C实现,self就是捕获的外部变量x static PyObject* add(PyObject* self, PyObject* args) { PyObject* y; // 解析传入的参数 if (!PyArg_ParseTuple(args, "O", &y)) { return NULL; } // 直接调用Python原生加法,支持所有实现了__add__的类型 return PyNumber_Add(self, y); } // add函数的方法定义 static PyMethodDef AddMethodDef = { .ml_name = "add", .ml_meth = add, .ml_flags = METH_VARARGS, .ml_doc = "Add captured x with input y" }; // 导出的get_add函数的C实现 static PyObject* mymodule_get_add(PyObject* self, PyObject* args) { PyObject* x; if (!PyArg_ParseTuple(args, "O", &x)) { return NULL; } // 增加x的引用计数,避免被提前回收 Py_INCREF(x); // 创建绑定了x作为上下文的可调用函数对象 PyObject* add_func = PyCFunction_NewEx(&AddMethodDef, x, NULL); // PyCFunction已经持有x的引用,释放本地引用避免泄漏 Py_DECREF(x); return add_func; } // 模块方法表 static PyMethodDef MyModuleMethods[] = { { "get_add", mymodule_get_add, METH_VARARGS, "Return a closure that adds input value with captured x" }, {NULL, NULL, 0, NULL} // 哨兵结束标记 }; // 模块定义结构体 static struct PyModuleDef mymodulemodule = { PyModuleDef_HEAD_INIT, .m_name = "mymodule", .m_doc = "Demo module for closure implementation with Python C API", .m_size = -1, .m_methods = MyModuleMethods }; // 模块初始化函数 PyMODINIT_FUNC PyInit_mymodule(void) { return PyModule_Create(&mymodulemodule); }
2. 编译配置(setup.py)
from setuptools import setup, Extension setup( name="mymodule", version="1.0", description="Python C API closure demo", ext_modules=[Extension("mymodule", sources=["mymodule.c"])] )
执行以下命令完成编译,编译完成后当前目录会生成对应平台的扩展模块文件(.so/.pyd):
python setup.py build_ext --inplace
3. 功能测试
测试代码和效果和原生Python完全一致:
from mymodule import get_add add5 = get_add(5) add10 = get_add(10) print(add5(12)) # 输出17 print(add10(12)) # 输出22 # 也支持其他可相加类型,比如字符串 add_hello = get_add("hello ") print(add_hello("world")) # 输出hello world
注意事项
- 捕获外部变量时必须主动调用
Py_INCREF增加引用计数,PyCFunction销毁时会自动对绑定的上下文执行Py_DECREF,不提前增加引用会导致变量被提前回收引发野指针错误。 - 本实现直接调用
PyNumber_Add做加法,和原生Python闭包的行为完全一致,支持所有实现了__add__魔术方法的Python类型。
内容的提问来源于stack exchange,提问作者Vipul Cariappa
相关产品推荐
相关产品推荐

