使用 pybind11 包装 yaml-cpp 迭代器
using pybind11 to wrap yaml-cpp iterator
我正在尝试用 pybind11 包装一些 yaml-cpp 代码。我意识到有一个用于操作 yaml 文件的 python 模块,但我很感激这种方法的帮助。我只是想熟悉 pybind11。
具体来说,我想为 YAML::Node
包装迭代器,但是迭代器中的 return 类型不是 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'
你很接近。如果您查看源代码,YAML::detail::iterator_value
扩展了 YAML::Node
,因此您必须在 python 代码中考虑到这一点。它还扩展了 std::pair<YAML::Node, YAML::Node>
,因此也需要以某种方式加以考虑。
struct iterator_value : public Node, std::pair<Node, Node> {
当它被绑定时,我们必须确保 Node 被绑定为父节点 class。看起来像:
py::class_<YAML::detail::iterator_value, YAML::Node>(m, "YamlDetailIteratorValue")
现在你在迭代时拥有了所有的 Node 方法,这很好!但是你会 运行 陷入真正的麻烦,因为 iterator_value
也继承自 std::pair
。据我所知,在 pybind11 中没有办法仅将其用作父类型,即使它具有自动转换对(有 bind_vector
和 bind_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
类型绑定了 first
和 second
,这样您就可以循环访问映射值。你可以打开 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
调用正常工作。
我正在尝试用 pybind11 包装一些 yaml-cpp 代码。我意识到有一个用于操作 yaml 文件的 python 模块,但我很感激这种方法的帮助。我只是想熟悉 pybind11。
具体来说,我想为 YAML::Node
包装迭代器,但是迭代器中的 return 类型不是 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'
你很接近。如果您查看源代码,YAML::detail::iterator_value
扩展了 YAML::Node
,因此您必须在 python 代码中考虑到这一点。它还扩展了 std::pair<YAML::Node, YAML::Node>
,因此也需要以某种方式加以考虑。
struct iterator_value : public Node, std::pair<Node, Node> {
当它被绑定时,我们必须确保 Node 被绑定为父节点 class。看起来像:
py::class_<YAML::detail::iterator_value, YAML::Node>(m, "YamlDetailIteratorValue")
现在你在迭代时拥有了所有的 Node 方法,这很好!但是你会 运行 陷入真正的麻烦,因为 iterator_value
也继承自 std::pair
。据我所知,在 pybind11 中没有办法仅将其用作父类型,即使它具有自动转换对(有 bind_vector
和 bind_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
类型绑定了 first
和 second
,这样您就可以循环访问映射值。你可以打开 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
调用正常工作。