【问题标题】:How to permit None or a specific type as an argument to a Python C Extension function?如何允许 None 或特定类型作为 Python C 扩展函数的参数?
【发布时间】:2017-07-09 01:41:53
【问题描述】:

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

静态 int foo(PyObject *self, PyObject *args) { 字符 *a = ""; 字符 *b = ""; 诠释 c = 0; if (!PyArg_ParseTuple(args, "ss|i", &a, &b, &c) { 返回空值; } printf("c 是 %i\n", c); //some_function_requiring_int_data_type(c); }

我希望用户能够提交intNone 作为c arg 的值,但上面的代码不允许这样做:

>>>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

目前,为了启用这种行为,我有一堆丑陋的代码,如下所示:

静态 int foo(PyObject *self, PyObject *args) { 字符 *a = ""; 字符 *b = ""; PyObject *c = NULL; // 注意我如何使用 PyObject * 诠释 c_int = 0; // 注意我有一个伴随的 int if (!PyArg_ParseTuple(args, "ss|O", &a, &b, &c) { 返回空值; } // 丑陋的代码从这里开始 如果 (c) { 如果(c!= Py_None){ 如果(!PyInt_Check(c)){ PyErr_SetString(PyExc_TypeError, "c 必须是 int 或 None"); 返回; } c_int = PyInt_AsSize_t(c); } } printf("c_int 是 %i\n", c_int); //some_function_requiring_int_data_type(c_int); }

及其用途:

>>>from spam import foo
>>>foo('a', 'b')
c is 0
>>>foo('a', 'b', 100)
c is 100
>>>foo('a', 'b', None)
c is 0

【问题讨论】:

  • 如果可能的话,我会将其作为仅关键字参数(那么就无需将 None 作为占位符值传递)。失败我会使用“O&”和转换器功能。这也有很多丑陋的代码,但它至少在一个清晰的独立单元中。 (如果有帮助,我可以发布代码,但应该很容易弄清楚)。不幸的是,使用 C api 很难完全避免丑陋的代码。
  • @DavidW 感谢converter function 的提示。请您查看我的答案 - 我对此有一些担忧。此外,您是否愿意发布您的建议以使用仅关键字参数作为答案?
  • 我已经发布了我的转换器功能版本。与您的主要区别在于它不会更改None 上的任何内容或错误,因此可以由调用者设置默认值。除非我遗漏了什么,否则我认为您的不应该出现段错误。稍后我将尝试添加仅关键字示例...
  • ... 并且只添加了关键字示例,我认为这不太难看。

标签: python python-2.7 python-c-api


【解决方案1】:

我的第一个建议是仅使用关键字参数。这样做的主要优点是避免传递 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);
}

(在 Python 3 中,您可以删除长度检查并使用 "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)

缺点是该方法并不总是有效 - 有时您需要该函数符合第 3 方库强制执行的固定接口。


我的第二个建议是使用转换器功能。这不会消除任何样板,而是将它们全部保存在一个包含良好且可重复使用的地方。这里的版本适用于 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,它总是会给你一个对象。
  • 如果发生故障或None,我们不会更改指针。这允许在调用函数中设置默认值。
  • 转换为 C 整数类型后,我们必须检查是否发生错误,因为如果整数太大,可能会出现溢出错误。在这种情况下,已经设置了正确的异常,所以我们只需要返回 0 来表示失败。 (我认为这与 Python 2 无关,因为它使用单独的大整数和小整数类型)

这些建议都没有真正回答所提出的问题,但它们确实提供了我认为更清洁的替代方案。

【讨论】:

    【解决方案2】:

    使用converter function 是一种方法。感谢@DavidW 的提示。

    我确实有一些问题:

    • 如果我没有传入正确的数据类型,我现在很可能会导致段错误
    • 要求在 null 时,int 值只能是一个值(本例中为 0)。它不能被泛化,以至于在不同的情况下我想要默认值(比如 -1)
    • 我必须对异常消息进行硬编码(“c 必须是 int”),所以我不能真正将它重用于不同的变量

    如果有人有解决方法,请将其发布为答案。

    静态 int_or_none(PyObject *python, void *c) { 国际温度 = 0; 如果(蟒蛇){ 如果(蟒蛇!= PyNone){ 如果(!PyInt_Check(python)){ PyErr_SetString(PyExc_TypeError, "c 必须是 int"); 返回0; } tmp = PyInt_AsSsize_t(python); if (tmp 0, not %i", tmp); 返回0; } } } *((int *) c) = tmp; 返回0; } 静态 int foo(PyObject *self, PyObject *args) { 字符 *a = ""; 字符 *b = ""; 诠释 *c = NULL; // 如果我不小心把它变成了 char *c,它可能会出现段错误 if (!PyArg_ParseTuple(args, "ss|O&", &a, &b, &int_or_none, &c) { 返回空值; } printf("c_int 是 %i\n", c_int); //some_function_requiring_int_data_type(c_int); }

    【讨论】:

      猜你喜欢
      • 2020-11-12
      • 2017-04-02
      • 1970-01-01
      • 1970-01-01
      • 2021-01-05
      • 2014-09-14
      • 2021-10-04
      • 1970-01-01
      • 2020-07-17
      相关资源
      最近更新 更多