Python C扩展__init__抛异常引发SystemError,pytest测试失败如何解决?
问题描述
我在用pytest测试自定义Python C扩展模块,想要验证当__init__方法传入无效类型参数时,能否正确抛出TypeError。
我的C实现代码如下:
PyObject * myObject_init(myObject *self, PyObject *args) { if ("# args are invalid") { PyErr_SetString(PyExc_TypeError, "Invalid Argument"); return NULL; } }
这段代码本应触发TypeError,但使用如下pytest测试用例时:
def test_init_with_invalid_argument(): x = "something invalid" with pytest.raises(TypeError): obj = MyObject(x)
测试失败,报错信息如下:
TypeError: Invalid Argument The above exception was the direct cause of the following exception: self = <test_mymodule.TestMyObjectInit object at 0x00000239886D27F0> def test_init_with_invalid_argument(self): with pytest.raises(TypeError): > obj = MyObject(x) E SystemError: <class 'mymodule.MyObject'> returned a result with an error set tests\test_init_with_invalid_argument.py:19: SystemError
问题原因
你的C扩展中__init__方法的实现违反了Python C API的规范:
- Python对象的
__init__方法对应C API中的tp_init成员,它的函数签名必须返回int类型,而非PyObject*。返回0表示初始化成功,返回-1表示初始化失败且已设置异常。 - 你错误地使用了
PyObject*作为返回类型,并在失败时返回NULL,这是tp_new(对应Python中的__new__)的返回逻辑。Python解释器调用tp_init时,无法识别这个返回值,因此会抛出SystemError,即使你已经正确设置了TypeError。
解决方法
修改C代码的函数签名和返回值,符合tp_init的规范:
- 调整函数的返回类型为
int,并补充可选的关键字参数(kwds)参数:
// 注意签名:返回int,新增kwds参数(即使不用也要保留,符合API规范) int myObject_init(myObject *self, PyObject *args, PyObject *kwds) { // 替换成真实的参数校验逻辑,示例:检查是否为整数类型 PyObject *arg; if (!PyArg_ParseTuple(args, "O", &arg) || !PyLong_Check(arg)) { PyErr_SetString(PyExc_TypeError, "Invalid Argument: expected integer"); return -1; // 返回-1表示初始化失败 } // 这里写正常的初始化逻辑 return 0; // 返回0表示初始化成功 }
- 在类型对象定义中,确保
tp_init指向这个正确的函数:
static PyTypeObject myObjectType = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "mymodule.MyObject", .tp_basicsize = sizeof(myObject), .tp_init = (initproc)myObject_init, // 绑定正确的初始化函数 // 其他类型定义字段... };
修改完成后重新编译扩展模块,再运行pytest测试,就能正确捕获到TypeError,测试通过。
内容的提问来源于stack exchange,提问作者agongji
相关产品推荐
相关产品推荐

