【问题标题】:using pybind11 to wrap yaml-cpp iterator使用 pybind11 包装 yaml-cpp 迭代器
【发布时间】:2020-06-12 15:32:01
【问题描述】:

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

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

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'

【问题讨论】:

    标签: c++ pybind11 yaml-cpp


    【解决方案1】:

    你很接近。如果您查看源代码,YAML::detail::iterator_value 扩展了YAML::Node,因此您必须在 python 代码中说明这一点。它还扩展了std::pair&lt;YAML::Node, YAML::Node&gt;,因此也需要以某种方式加以考虑。

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

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

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

    现在您在迭代时拥有了所有 Node 方法,这很好!但是你会遇到真正的麻烦,因为iterator_value 也继承自std::pair。据我所知,没有办法只将它用作 pybind11 中的父类型,即使它具有对的自动转换(有 bind_vectorbind_map,但没有 bind_pair)。我认为您可以为此类事情编写自己的绑定,但我不确定是否有必要。您真正需要做的是检查您将要迭代的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, "");
    

    我绑定了 NodeType 枚举,因此当您在节点上调用 type 时,您可以将其公开。然后我为iterator_value 类型绑定了firstsecond,这样您就可以在循环中访问映射值。你可以打开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) 代码使用绑定的 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__ 在地图上的工作与在iterator_value 上调用.second 时一样好。您可能希望在整数上覆盖 __get__,因此它也可以让您进行序列访问。

    您还有一个额外的 __str__ 方法,可以让所有 print 调用正常工作。

    【讨论】:

      猜你喜欢
      • 2020-08-28
      • 2023-03-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多