首页 > 其他分享 >Pybind11 揭秘。 Ch 4.1:仅位置参数

Pybind11 揭秘。 Ch 4.1:仅位置参数

时间:2022-09-05 10:45:11浏览次数:83  
标签:返回 std Ch 4.1 tuple PyObject PyTuple xx Pybind11

Pybind11 揭秘。 Ch 4.1:仅位置参数

Python 不支持 C++ 中的函数重载(具有相同函数名称的不同函数签名)。尽管如此,Python 确实提供了一组少数语法,包括默认值和 kwargs,以允许根据参数改变函数的行为。

本章将探讨在 Python 扩展中处理 Python 风格的函数重载的不同方法,以及如何为它们编写 C 扩展。

Ch 4.1 仅位置参数

根据扩展函数是否使用任何位置参数,它将被调用 METH_VARARGS 或者 METH_VARARGS | METH_KEYWORDS 公约。 详情可查 这里 .

在本章中,我们将关注仅具有位置参数的函数,即 METH_VARARGS 惯例。

来看看我们的 xx模块.c

 静态 PyMethodDef xx_methods[] = {  
 {“roj”,xx_roj,METH_VARARGS,  
 PyDoc_STR("roj(a,b) -> 无")},  
 {“foo”,xx_foo,METH_VARARGS,  
 xx_foo_doc},  
 {“新”,xx_new,METH_VARARGS,  
 PyDoc_STR("new() -> new Xx object")},  
 {“错误”,xx_bug,METH_VARARGS,  
 PyDoc_STR("bug(o) -> 无")},  
 {NULL, NULL} /* 哨兵 */  
 };

这个列表定义了所有模块的功能,让我们通过扩展列表来添加一个新的。就我而言,我称之为 xx_arg 骨架实现如下。

 静态 PyObject *  
 xx_arg(PyObject *self, PyObject *args)  
 {  
 Py_IncRef(Py_None);  
 返回 Py_None;  
 }

请注意,从 C 扩展函数返回的任何内容都将被一个变量捕获,并且当捕获变量超出范围时,其引用计数将减少。所以我们需要系统地 将返回值的引用计数增加 1 以避免负引用计数。这同样适用于 None,一个定义的 Python 对象 这里 .

除了返回 None,我们还可以返回 参数 看看它是什么。

 静态 PyObject *  
 xx_arg(PyObject *self, PyObject *args)  
 {  
 Py_IncRef(args);  
 返回参数;  
 }

然后在 Python 中:

 >>> 进口 xx  
 >>> 打印(xx.arg(9.,2.5))  
 (9.0, 2.5)

好的。 参数 只是输入参数的元组,基本上 *参数 .

或者我们也可以检查 a 的类型 PyObject , 和 Py*_检查 方法,例如 PyTuple_Check

在 C 扩展中检查变量类型的另一种方法是利用[ PyTypeObject](https://github.com/python/cpython/blob/6fd4c8ec7740523bb81191c013118d9d6959bc9d/Include/object.h#L134) .

 printf(“类型:%s\n”, Py_TYPE(args)->tp_name);

甚至更好,与 PyObject_Print ,您可以打印对象的字符串表示形式。

一旦我们确定变量是 PyTuple ,我们可以通过 PyTuple_Size .我们也可以得到它的第 i 个孩子 PyTuple_GET_ITEM

将所有这些放在一起,我们可以得到第二个版本,如下所示:

 静态 PyObject *  
 xx_arg(PyObject *self, PyObject *args)  
 {  
  
 断言(PyTuple_Check(args)); Py_ssize_t 计数 = PyTuple_Size(args);  
 for (int i = 0; i < 计数; i++)  
 {  
 printf("类型: %s\n", Py_TYPE(element)->tp_name);  
 } Py_IncRef(Py_None);  
 返回 Py_None;  
 }

看起来很棒。但这很长;如果我可以使用 C/C++ 类型函数定义一个函数,而不是手动将元组中的每个 PyObject 转换为其对应的类型,那就太好了,这正是 Pybind11 所做的,使用复杂的 C++ 模板内容。

让我们做一个最小的重新实现以使事情变得清晰。

我们需要做的第一件事就是改变 xx模块.c xx模块.cc 并使用 g++它 代替 海合会 作为编译器。

然后我们需要以 C/C++ 风格定义实际的函数体。

 int func(int a,float b)  
 {  
 返回 a + b + 1;  
 }

最后,我们需要完成将 PyObject* 元素从元组转换为其对应的 C 类型的困难部分。

对于每种类型,让我们定义一个 过程 转换函数 PyObject* 到所需的类型。这是我的演示实现。

 模板<typename Arg>  
 参数处理(PyObject *op); 模板<>  
 整数进程<int>(PyObject *操作)  
 {  
 int v = _PyLong_AsInt(op);  
 printf("处理 int: %d\n", v);  
 返回 v;  
 } 模板<>  
 肺突<long>(PyObject *操作)  
 {  
 长 v = PyLong_AsLong(op);  
 printf("进程长:%ld\n", v);  
 返回 v;  
 } 模板<>  
 浮动过程<float>(PyObject *操作)  
 {  
 浮动 v = PyFloat_AsDouble(op);  
 printf("处理浮点数:%lf", v);  
 返回 2.;  
 }

然后让我们将每个处理过的 PyObject 放入一个元组并将其传递给目标 F 功能。提供 C++ 17 标准::应用 对于这项工作非常方便。这是完整的代码。

 模板<typename Return, typename... Args, std::size_t... Idx>  
 Return _expand(Return (*f)(Args...), PyObject *tuple, std::index_sequence<Idx...> )  
 {  
 返回 std::apply(f, std::make_tuple(进程<Args>(PyTuple_GetItem(tuple, Idx))...));  
 } 模板<typename Return, typename... Args>  
 返回扩展(返回(* f)(Args ...),PyObject *元组)  
 {  
 return _expand(f, tuple, std::make_index_sequence<sizeof...(Args)> ());  
 }

乍一看似乎有点复杂,但实际上很简单。

std::make_index_sequence<sizeof…(Args)> () 返回一个由 0、1、2、...、N 组成的序列,其中 N 是目标中的参数数量 F 功能。

std::make_tuple(进程<Args>(PyTuple_GetItem(tuple, Idx))…) 意味着我们正在创建一个已处理元素的元组。 ... 的一般经验法则是在参数包中的每个值的点之前重复该语句。展开形式与以下类似。

 std::make_tuple(  
 过程<Arg0>(PyTuple_GetItem(tuple, 0)),  
 过程<Arg1>(PyTuple_GetItem(tuple, 1)),  
 过程<Arg2>(PyTuple_GetItem(tuple, 2)),  
 ...  
 过程<ArgN>(PyTuple_GetItem(元组,N)))

标准::应用 扩展此元组并将其应用于 F , IE

 F(  
 过程<Arg0>(PyTuple_GetItem(tuple, 0)),  
 过程<Arg1>(PyTuple_GetItem(tuple, 1)),  
 过程<Arg2>(PyTuple_GetItem(tuple, 2)),  
 ...  
 过程<ArgN>(PyTuple_GetItem(元组,N)))

很酷。不是吗?

但是我们遗漏了一些东西……类型检查怎么样?如果传递的参数与所需的 C 类型不兼容怎么办?

让我们将类型检查作为练习留给您。使用上面的示例,您应该能够编写该代码。

(在完成练习之前不要翻页。)

这是一个参考实现。例如,我相信您可以获得更好的版本,并提供更有意义的错误报告(请在评论中发布您的版本!)。

 模板<typename Arg>  
 布尔检查(PyObject *)  
 {  
 返回假;  
 } 模板<>  
 布尔检查<long>(PyObject *操作)  
 {  
 返回 PyLong_CheckExact(op);  
 } 模板<>  
 布尔检查<float>(PyObject* 开启){  
 返回 PyFloat_CheckExact(op);  
 } 模板<typename Return, typename... Args>  
 返回扩展(返回(* f)(Args ...),PyObject *元组)  
 {  
 return _expand(f, tuple, std::make_index_sequence<sizeof...(Args)> ());  
 } 模板<typename Return, typename... Args, std::size_t... Idx>  
 Return _expand(Return (*f)(Args...), PyObject *tuple, std::index_sequence<Idx...> )  
 {  
 **自动检查 = std::initializer_list {检查 <Args>(PyTuple_GetItem(tuple, Idx))...};  
 for (int i = 0; i < checks.size(); i++)  
 {  
 if (!*(checks.begin() + i))  
 {  
 printf("发现 %d arg\n 有问题", i);  
 返回返回();  
 }  
 }**  
 返回 std::apply(f, std::make_tuple(进程<Args>(PyTuple_GetItem(tuple, Idx))...));  
 }

好的。这就是本章的内容。现在通过调用尝试一下 展开(YOUR_TARGET_FUNCTION,ARGS_TUPLE) 在您新创建的函数中。

您可能已经注意到,这种调用转换需要一个中间 Python 元组,而它可能只是使用了一个低级 C 样式的数组。 https://bugs.python.org/issue29259 提出了一种称为快速调用的新转换。我们将很快介绍这一点。

第 4.2 章:待定

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明

本文链接:https://www.qanswer.top/15630/37370510

标签:返回,std,Ch,4.1,tuple,PyObject,PyTuple,xx,Pybind11
From: https://www.cnblogs.com/amboke/p/16657261.html

相关文章