使用C语言编写Python扩展2——函数

上一节介绍了编写扩展的基本流程。这一回介绍一下在扩展模块中的函数调用,包括在扩展函数的参数提取和关键字参数解析,以及在C语言中调用Python方法。

同样的本文中的示例代码可从 https://github.com/wusuopu/python-c-extension-sample 获取到。

参数提取

接着上一节的例子,我们继续编辑lc_hello.c文件。先往模块中添加一个名为 func1 的函数,即就是在 lc_hello_world_methods 数组中添加一项:

{"func1", (PyCFunction)func1_function, METH_VARARGS, NULL},

然后就是对该函数的实现。
参数提取是使用 PyArg_ParseTuple 方法,其定义如下:

int PyArg_ParseTuple(PyObject *arg, char *format, ...);

其中 arg 参数为Python向C函数传递的参数列表,是一个无组对象;format 参数是一个格式化字符串,它的格式可以参考 Python/C API 文档。
func1_function 函数实现如下:

static PyObject* func1_function(PyObject *self, PyObject *args)
{
    int num, i, j;
    long lnum=0;
    const char* s1 = NULL;
    PyObject *obj = NULL;
    if (!PyArg_ParseTuple(args, "is(ii)|l",
                          &num, &s1, &i, &j, &lnum)) {
        printf("传入参数错误!\n");
        return NULL;
    }
    printf("num: %d\tstr1: %s\n"
           "i: %d\tj: %d\tlnum: %ld\n",
           num, s1, i, j, lnum);

    obj = Py_BuildValue("{sisisislss}",
                        "num", num, "i", i, "j", j, "lnum", lnum, "s1", s1);
    return obj;
}

在Python中该函数可以接收3个或者4个参数。同时该函数使用了 Py_BuildValue 方法构造了一个字典对象并返回。Py_BuildValue的用法与PyArg_ParseTuple类似。
接下来可以在Python中进行测试:

print(lc_hello_world.func1(11, 'abc', (2, 3), 100))
print(lc_hello_world.func1(11, 'abc', (2, 3)))

关键字参数

再在 lc_hello_world_methods 数组中添加一项:

{"func2", (PyCFunction)func2_function, METH_VARARGS | METH_KEYWORDS, NULL},

关键字参数解析是使用 PyArg_ParseTupleAndKeywords 方法,其定义如下:

int PyArg_ParseTupleAndKeywords(PyObject *arg, PyObject *kwdict,
                                char *format, char *kwlist[], ...);

其中 arg 参数和 format 参数与PyArg_ParseTuple一样。kwdict参数是一个字典对象,保存了关键字参数。kwlist是一个以NULL结尾的字符串数组。
func2_function 函数实现如下:

static PyObject* func2_function(PyObject *self, PyObject *args, PyObject *kwargs)
{
    int voltage;
    char *state = "a stiff";
    char *action = "voom";
    char *type = "Norwegian Blue";

    static char *kwlist[] = {"voltage", "state", "action", "type", NULL};

    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|sss", kwlist,
                                     &voltage, &state, &action, &type))
        return NULL;

    printf("-- This parrot wouldn't %s if you put %i Volts through it.\n",
           action, voltage);
    printf("-- Lovely plumage, the %s -- It's %s!\n", type, state);
    Py_INCREF(Py_None);
    return Py_None;
}

接下来可以在Python中进行测试:

lc_hello_world.func2(state="ok", action="test", type="func", voltage=13)
lc_hello_world.func2(20)

在扩展模块中调用Python方法

在扩展模块中可以使用 PyObject_CallObject 方法来调用Python的函数方法。其定义如下:

PyObject* PyObject_CallObject(PyObject *callable_object, PyObject *args)

再在 lc_hello_world_methods 数组中添加一项:

{"func3", (PyCFunction)func3_function, METH_VARARGS, NULL},

func3_function 函数实现如下:

static PyObject* func3_function(PyObject *self, PyObject *args)
{
    PyObject *my_callback = NULL;
    PyObject *result = NULL;
    PyObject *arg = NULL;
    if (!PyArg_ParseTuple(args, "OO:set_callback;argument;", &my_callback, &arg)) {
        printf("传入参数错误!\n");
        return NULL;
    }
    if (!PyCallable_Check(my_callback)) {
        PyErr_SetString(PyExc_TypeError, "parameter must be callable");
        return NULL;
    }
    result = PyObject_CallObject(my_callback, arg);
    if (!result) {
        Py_INCREF(Py_None);
        result = Py_None;
    }
    return result;
}

接下来可以在Python中进行测试:

print(lc_hello_world.func3(int, (1.234, )))