如何允许None或特定类型作为Python C扩展函数的参数?

如何允许None或特定类型作为Python C扩展函数的参数?,python,python-2.7,python-c-api,Python,Python 2.7,Python C Api,假设我有一个人为的函数,如下所示: 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\n", c); //some_function_

假设我有一个人为的函数,如下所示:

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\n", c); //some_function_requiring_int_data_type(c); } 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)) { PyErr_SetString(PyExc_TypeError, "c must be int or None"); return; } c_int = PyInt_AsSize_t(c); } } printf("c_int is %i\n", c_int); //some_function_requiring_int_data_type(c_int); }
目前,为了启用此行为,我有一堆丑陋的代码,如下所示:

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\n", c); //some_function_requiring_int_data_type(c); } 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)) { PyErr_SetString(PyExc_TypeError, "c must be int or None"); return; } c_int = PyInt_AsSize_t(c); } } printf("c_int is %i\n", c_int); //some_function_requiring_int_data_type(c_int); } 使用a是一种方法。谢谢@DavidW的提示

我确实有一些问题:

  • 如果我没有传递正确的数据类型,我现在很容易导致SEGFULTS
  • 它要求在null上,int值只能是一个值(本例中为0)。它不能被泛化,因此在不同的情况下,我需要默认值(如-1)
  • 我必须硬编码异常消息(“c必须是int”),所以我不能真正地将其用于其他变量
如果有人有工作,请将其作为答案发布

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\n", c_int); //some_function_requiring_int_data_type(c_int); } 静态int_或无(PyObject*python,void*c){ 内部温度=0; if(python){ if(python!=PyNone){ 如果(!PyInt_Check(python)){ PyErr_SetString(PyExc_TypeError,“c必须是int”); 返回0; } tmp=PyInt\u AsSsize\u t(python); 如果(tmp 0,而不是%i),tmp; 返回0; } } } *((int*)c)=tmp; 返回0; } 静态int-foo(PyObject*self,PyObject*args){ char*a=“”; char*b=“”; int*c=NULL;//如果我不小心将其设置为char*c,它可能会出错 if(!PyArg_ParseTuple(args,“ss | O&“,&a,&b,&int_或_none,&c){ 返回NULL; } printf(“c_int是%i\n”,c_int); //某些函数需要输入数据类型(c输入); }
我的第一个建议是只使用关键字参数。这种方法的主要优点是避免传入
None
占位符值,因为您永远不必“填充”(比如)未指定的第三个位置参数,这样您就可以指定第四个。它基本上是将Python接口更改为“匹配您的意思”“再多一点

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\n", c);
    return PyLong_FromSsize_t(c);
}
(在Python3中,您可以删除长度检查,并使用
“ss |$i”
指定
$
后面的参数仅为关键字,这稍微好一点)。您需要将函数类型指定为
METH_VARARGS | METH_KEYWORDS

然后,您可以从Python将其称为

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
但不是

int_from_kw("something","something else",c="not an int")
int_from_kw("something","something else",5)
缺点是这种方法并不总是有效的——有时您需要函数符合第三方库强制执行的固定接口


我的第二个建议是使用转换函数。这并没有消除任何锅炉板,而是将其保存在一个包含良好且可重复使用的位置。这里的版本适用于Python 3(因为我安装了它!),但我认为Python 2的主要变化是将
PyLong
替换为
PyInt

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\n", c);
    return PyLong_FromSsize_t(c);
}
一些简要说明(参考您的版本):

  • 我们确信
    o
    永远不会
    NULL
    ,因为它来自Python,Python将始终为您提供一个对象
  • 如果出现故障或
    None
    我们不会更改指针。这允许在调用函数中设置默认值
  • 转换为C整数类型后,我们必须检查是否发生错误,因为如果整数太大,则可能会出现溢出错误。在这种情况下,已设置了正确的异常,因此我们只需返回0表示失败。(我认为这与Python 2无关,因为它使用不同的大整数类型和小整数类型)


这两个建议都没有真正回答问题,但它们确实提供了我认为更干净的替代方案。

如果可能,我会将其作为只包含关键字的参数(这样就没有必要将
None
作为占位符值传递)。否则,我会使用“O&”还有一个转换函数。它有很多难看的代码,但至少在一个清晰的独立单元中。(如果有帮助,我可以发布代码,但应该很容易理解)。不幸的是,使用C api很难完全避免难看的代码。@DavidW感谢使用
转换函数的技巧。
。请查看我的答案,我对此有一些顾虑。此外,您是否希望发布您的建议,以仅使用关键字参数作为答案?我已经发布了我的转换版本函数。与您的主要区别在于它不会更改
None
上的任何内容或错误,因此默认值可以由调用方设置。我认为您的函数不应该设置错误,除非我遗漏了一些内容。稍后我将尝试添加一个仅关键字的示例……并且添加了一个仅关键字的示例,我不认为这太难看。