c++ - 使用 pybind11 包装 yaml-cpp 迭代器

标签 c++ pybind11 yaml-cpp

我正在尝试使用 pybind11 包装一些 yaml-cpp 代码。我意识到有一个用于操作 yaml 文件的 python 模块,但我将不胜感激这种方法的帮助。我只是想熟悉 pybind11。

具体来说,我想将迭代器包装为 YAML::Node ,但迭代器的返回类型不是 YAML::Node ,它是 YAML::detail::iterator_value 。我如何从这种类型返回到 YAML::Node在迭代器 lambda 函数中?这是我的代码的相关部分。

utilities_py.cc

#include "yaml-cpp/yaml.h"
#include "pybind11/pybind11.h"

PYBIND11_MODULE(utilities, m) {
  namespace py = pybind11;

    py::class_<YAML::detail::iterator_value>(m, "YamlDetailIteratorValue")
        .def(py::init<>());

    py::class_<YAML::Node>(m, "YamlNode")
        .def(py::init<const std::string &>())
        .def("__getitem__",
            [](const YAML::Node node, const std::string key){
              return node[key];
            })
        .def("__iter__",
            [](const YAML::Node &node) {
              return py::make_iterator(node.begin(), node.end());},
             py::keep_alive<0, 1>());

    m.def("load_file", &YAML::LoadFile, "");
}

test_utilities_py.py

from utilities import load_file

test_node = load_file('test.yaml')
for nodelette in test_node:
    prop = nodelette['prop']

我收到以下错误:

TypeError: __getitem__: incompatible function arguments. The following argument types are supported:
    1. (arg0: utilities.YamlNode, arg1: str) -> utilities.YamlNode

Invoked with: <utilities.YamlDetailIteratorValue object at 0x7f8babc446f0>, 'prop'

最佳答案

你很接近。如果你看一下源代码,YAML::detail::iterator_value延伸YAML::Node ,所以你必须在 python 代码中考虑到这一点。它还扩展了 std::pair<YAML::Node, YAML::Node> ,因此也需要以某种方式加以考虑。

struct iterator_value : public Node, std::pair<Node, Node> {

当它被绑定(bind)时,我们必须确保 Node 被绑定(bind)为父类。这看起来像:

py::class_<YAML::detail::iterator_value, YAML::Node>(m, "YamlDetailIteratorValue")

现在您在迭代时拥有了所有 Node 方法,这很好!但你会遇到真正的麻烦,因为 iterator_value也继承自std::pair 。据我所知,没有办法只将其用作 pybind11 中的父类型,即使它具有成对的自动转换功能(有 bind_vectorbind_map 但没有 bind_pair )。我认为你可以为这样的事情编写自己的绑定(bind),但我不确定这是必要的。实际上,您需要做的是检查 Node 的类型您将要迭代,然后根据它是映射还是序列进行稍微不同的迭代(这类似于 c++ api 的工作方式,其中序列和映射都有一个迭代器类型,但某些函数将失败,如果在错误的上下文中调用)。

这是我最终解决问题的方法:

PYBIND11_MODULE(utilities, m) {
    py::enum_<YAML::NodeType::value>(m, "NodeType")
    .value("Undefined", YAML::NodeType::Undefined)
    .value("Null", YAML::NodeType::Null)
    .value("Scalar", YAML::NodeType::Scalar)
    .value("Sequence", YAML::NodeType::Sequence)
    .value("Map", YAML::NodeType::Map);

    py::class_<YAML::Node>(m, "YamlNode")
        .def(py::init<const std::string &>())
        .def("__getitem__",
            [](const YAML::Node node, const std::string& key){
              return node[key];
            })
        .def("__iter__",
            [](const YAML::Node &node) {
              return py::make_iterator(node.begin(), node.end());},
             py::keep_alive<0, 1>())
        .def("__str__",
             [](const YAML::Node& node) {
               YAML::Emitter out;
               out << node;
               return std::string(out.c_str());
             })
        .def("type", &YAML::Node::Type)
        .def("__len__", &YAML::Node::size)
        ;

    py::class_<YAML::detail::iterator_value, YAML::Node>(m, "YamlDetailIteratorValue")
        .def(py::init<>())
        .def("first", [](YAML::detail::iterator_value& val) { return val.first;})
        .def("second", [](YAML::detail::iterator_value& val) { return val.second;})
        ;

    m.def("load_file", &YAML::LoadFile, "");

我绑定(bind)了 NodeType 枚举,因此当您调用 type 时,您可以将其公开。在一个节点上。然后我绑定(bind)firstsecond对于iterator_value类型,以便您可以循环访问 map 值。您可以打开type()弄清楚如何迭代。我的示例 yaml 文件

---
 doe: "a deer, a female deer"
 ray: "a drop of golden sun"
 pi: 3.14159
 xmas: true
 french-hens: 3
 calling-birds:
   - huey
   - dewey
   - louie
   - fred
 xmas-fifth-day:
   calling-birds: four
   french-hens: 3
   golden-rings: 5
   partridges:
     count: 1
     location: "a pear tree"
   turtle-doves: two

我的示例 python (3.8) 代码使用绑定(bind)的 c++

import example
from example import load_file

def iterator(node):
    if node.type() == example.NodeType.Sequence:
        return node
    elif node.type() == example.NodeType.Map:
        return ((e.first(), e.second()) for e in node)
    return (node,)

test_node = load_file('test.yml')


for key, value in iterator(test_node):
    if value.type() == example.NodeType.Sequence:
        print("list")
        for v in iterator(value):
            print(v)
    elif value.type() == example.NodeType.Map:
        print("map")
        for k,v in iterator(value):
            temp = value[str(k)]
            print(k, v)
            print(str(v) == str(temp))

演示不同类型的正确迭代以及 __get__ 的事实在 map 上的工作方式与调用 .second 时一样好关于iterator_value 。您可能想覆盖 __get__在整数上,所以它也可以让你进行序列访问。

您获得了奖金 __str__方法也是如此,使所有print调用工作。

关于c++ - 使用 pybind11 包装 yaml-cpp 迭代器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62347521/

相关文章:

python - pybind11,cmake : how to install files?

c++ - 第 0 行第 0 列错误 : bad conversion while loading YAML file

c++ - 为什么在创建入栈函数时使用指向栈的指针?

c++ - 如何使用智能指针正确销毁 C++ 类

c++ - 访问冲突 0xFEEEFEEE 无法访问组件

c++ - "Illegal block entry"以及 yaml-cpp 中的 YAML 怪物示例

c++ - 如何在 Sublime Text 上的 Windows/Cygwin 上构建 yaml-cpp?

c++ - 在多个 Cpp 文件中使用变量

python - 在pybind11中输入npz文件

cmake - 通过ExternalProject_Add 使用 pybind11 进行 CMake 项目的智能方法