Python 模块扩展之回调函数传参

在 C/C++ 中,Python 的数据类型皆为PyObject*,所有函数传参和其他的数字、字符串传参没有差异。区别在PyArg_ParseTuple(args, "O", &callback)的第二个参数 O(字母 O),数字是i,字符串是s

下面会举两个例子,一个是回调函数无参数的,另外一种是回调函数有参数。

Python 回调函数无参数

PyArg_ParseTuple的第二个参数为O(字母 O),PyObject_CallObject 执行回调函数调用,返回结果。
由于是无参数,所以PyObject_CallObject第二个参数为NULL

static PyObject* callback(PyObject* self, PyObject* args){
    PyObject *callback = NULL;
    PyObject *result = NULL;

    if (!PyArg_ParseTuple(args, "O", &callback)){
        return nullptr;
    }
    result = PyObject_CallObject(callback, NULL);
    Py_INCREF(result);
    return result;
}

Python 回调函数有参数

如果需要传递 C/C++ 中的变量作为参数,传入值回调函数中,我们需要构造一个PyOject *的结构作为第二个参数传递给PyObject_CallObject。如下会用到Py_BuildValue

Py_BuildValuePyArg_ParseTuple的参数绑定一致,如下:i表示传递一个int类型的数值,(i)表示这是一个int类型的元祖(tuple),因为函数参数需要一个元祖(tuple)的数据结构。

static PyObject* callback_with_args(PyObject* self, PyObject* args){
    PyObject *callback = NULL;
    PyObject *result = NULL;

    if (!PyArg_ParseTuple(args, "O", &callback)){
        return nullptr;
    }
    int arg = 123;
    PyObject* arg_list = Py_BuildValue("(i)", arg);
    result = PyObject_CallObject(callback, arg_list);
    Py_DECREF(arg_list);
    if (result == NULL) {
        return NULL;
    }
    Py_INCREF(result);
    return result;
}

编程 Python 程序测试

import sys

sys.path.append('./callback.so')

import callback

def f():
    print(2)

def f_with_args(i):
    print(i)


def f_with_args_return(i):
    return i


def main():
    # 无参数
    callback.callback(f)
    # 回调函数有参数
    callback.callback_with_args(f_with_args)
    print(callback.callback_with_args(f_with_args_return))


if __name__ == "__main__":
    main()

构建编译及测试

g++ -fpic -c \
  -I/Library/Frameworks/Python.framework/Versions/3.7/include/python3.7m \
    callback.cpp

g++ -shared -o callback.so callback.o -lstdc++ \
  -L/Library/Frameworks/Python.framework/Versions/3.7/lib/ \
  -lpython3.7m

python test2.py

# output:
# 2
# 123
# 123

扩展

dir是 Python 的内置函数,可以帮我们输出变量中的属性以及方法列表。如下是输出callback模块中的属性及方法列表。

pirnt(dir(callback))
# output: ['__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'callback', 'callback_with_args']

推荐阅读: