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