How to permit None or a specific type as an argument to a Python C Extension function?
假设我有如下的人为功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | static int foo(PyObject *self, PyObject *args) { char *a =""; char *b =""; int c = 0; if (!PyArg_ParseTuple(args,"ss|i", &a, &b, &c) { return NULL; } printf("c is %i ", c); //some_function_requiring_int_data_type(c); } |
我希望用户能够将
1 2 3 4 5 6 7 | >>>from spam import foo >>>foo('a', 'b') c is 0 >>>foo('a', 'b', 100) c is 100 >>>foo('a', 'b', None) TypeError: an integer is required |
目前,为了实现这种行为,我有一堆丑陋的代码,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | static int foo(PyObject *self, PyObject *args) { char *a =""; char *b =""; PyObject *c = NULL; // Note how I use PyObject * int c_int = 0; // Note how I have an accompanying int if (!PyArg_ParseTuple(args,"ss|O", &a, &b, &c) { return NULL; } // Ugly code starts here if (c) { if (c != Py_None) { if (!PyInt_Check(c)) { PyExc_SetString(PyExc_TypeError,"c must be int or None"); return; } c_int = PyInt_AsSsize_t(c); } } printf("c_int is %i ", c_int); //some_function_requiring_int_data_type(c_int); } |
及其用途:
1 2 3 4 5 6 7 | >>>from spam import foo >>>foo('a', 'b') c is 0 >>>foo('a', 'b', 100) c is 100 >>>foo('a', 'b', None) c is 0 |
型
我的第一个建议是只使用关键字参数。这样做的主要优点是避免了必须传递
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | static PyObject* int_from_kw(PyObject* self, PyObject* args, PyObject* kwargs) { char *a, *b; Py_ssize_t c = 0; // default value char* kwarg_names[] = {"a","b","c",NULL}; // optional check to ensure c is passed only as a keyword argument - not needed with Python 3 if (PyTuple_Size(args)>2) { PyErr_SetString(PyExc_TypeError,"Only two positional arguments allowed"); return NULL; } if (!PyArg_ParseTupleAndKeywords(args,kwargs,"ss|i",kwarg_names,&a,&b,&c)) { return NULL; } printf("c_int is %li ", c); return PyLong_FromSsize_t(c); } |
号
(在python 3中,您可以删除长度检查,并使用
然后您可以从python将其称为
1 2 3 | int_from_kw("something","something else") # default c int_from_kw("something","something else",c=5) int_from_kw(a="something",b="something else",c=5) # etc |
但不是
1 2 | int_from_kw("something","something else",c="not an int") int_from_kw("something","something else",5) |
。
缺点是这种方法并不总是有效——有时您需要该函数符合第三方库强制执行的固定接口。
我的第二个建议是使用转换器函数。这并没有消除任何锅炉板,但保持在一个良好的遏制和可重复使用的地方。这里的版本是针对python 3的(因为我已经安装了它!)但我认为python 2的主要变化是用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | int int_or_none(PyObject* o, void* i) { Py_ssize_t tmp; Py_ssize_t* i2 = i; if (o==Py_None) { return 1; // happy - leave integer as the default } if (PyLong_Check(o)) { tmp = PyLong_AsSize_t(o); if (PyErr_Occurred()) { return 0; } else { *i2 = tmp; return 1; } } PyErr_SetString(PyExc_TypeError,"c must be int or None"); return 0; // conversion failed } static PyObject* test_int_none(PyObject* self, PyObject* args) { char *a, *b; Py_ssize_t c = 0; // default value if (!PyArg_ParseTuple(args,"ss|O&", &a, &b, int_or_none, &c)) { return NULL; } printf("c_int is %i ", c); return PyLong_FromSsize_t(c); } |
一些简要说明(参考您的版本):
- 百万千克1我们相信,
型
这些建议都没有真正回答问题,但它们确实提供了我认为更清洁的选择。
型
使用转换器功能是一种方法。谢谢@davidw给你小费。
我确实有一些问题:
- 百万千克1如果我没有输入正确的数据类型,我现在可能会导致segfaults。百万千克1百万千克1它要求在为空时,int值只能是一个值(在本例中为0)。它不能被通用化,在不同的情况下,我需要默认值(如-1)百万千克1百万千克1我必须对异常消息进行硬编码("c必须是int"),因此我不能对其他变量重复使用它。百万千克1
型
如果有人在附近工作,请将其作为答案张贴。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | static int int_or_none(PyObject *python, void *c) { int temp = 0; if (python) { if (python != PyNone) { if (!PyInt_Check(python)) { PyErr_SetString(PyExc_TypeError,"c must be int"); return 0; } tmp = PyInt_AsSsize_t(python); if (tmp 0, not %i", tmp); return 0; } } } *((int *) c) = tmp; return 0; } static int foo(PyObject *self, PyObject *args) { char *a =""; char *b =""; int *c = NULL; // If I accidentally make this a char *c, it may segfault if (!PyArg_ParseTuple(args,"ss|O&", &a, &b, &int_or_none, &c) { return NULL; } printf("c_int is %i ", c_int); //some_function_requiring_int_data_type(c_int); } |