Gup*_*pta 5 c++ enums pybind11
假设我有一个像这样的 C++ 枚举:
enum class Kind {Kind1 = 1, Kind2, Kind3};
Run Code Online (Sandbox Code Playgroud)
要使用 Pybind11 将此枚举绑定到 Python 枚举中,我正在执行以下操作:
py::enum_<Kind>(py_module, "Kind")
.value("Kind1", Kind::Kind1)
.value("Kind2", Kind::Kind2)
.value("Kind3", Kind::Kind3)
.def("__len__",
[](Kind p) {
return 3;
});
Run Code Online (Sandbox Code Playgroud)
编译代码后,如果我询问枚举的长度,我将收到此错误:
>>> len(Kind)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: object of type 'pybind11_type' has no len()
Run Code Online (Sandbox Code Playgroud)
我有什么想法可以解决它吗?
编辑1:我在 Visual Studio 2019 (C++17) 上使用 Pybind11 版本 2.10.1。
编辑 2:我希望具有与 Python 枚举中相同的行为:
>>> from enum import Enum
>>> class Kind(Enum):
... kind1 = 1
... kind2 = 2
... kind3 = 3
...
>>> len(Kind)
3
Run Code Online (Sandbox Code Playgroud)
在开始之前,让我们解决一个相关问题——如何避免必须显式指定每个枚举返回的长度?如果有什么东西可以为我们做到这一点,或者如果我们可以在运行时动态计算它,那就太好了。
原来有办法。在阅读代码时,我发现了一个有趣的实现细节。pybind11 创建的枚举包装类有一个名为 的属性__entries。它是一本字典,每个枚举值保存一个条目,主要用于生成文档、获取值的文本表示形式以及将值导出到父范围。
以下是示例枚举的样子:
>>> print(Kind.__entries)
{'Kind1': (Kind.Kind1, None), 'Kind2': (Kind.Kind2, None), 'Kind3': (Kind.Kind3, None)}
Run Code Online (Sandbox Code Playgroud)
因此,我们可以len(Kind.__entries)在运行时获取正确的长度(枚举值的数量)。在 C++ 中,这就是类对象py::len(cls.attr("__entries"))所在的位置。clsKind
现在我们可以找到问题的根源——如何len在类对象而不是类实例上进行工作。根据这个 SO 答案,实现这一点的一种方法是使用元类。具体来说,我们需要枚举包装类使用具有__len__成员函数的元类,该元类将计算并返回包装类保存的值的数量。
事实证明,pybind 生成的包装类已经使用了名为的自定义元类pybind11_type:
>>> type(Kind)
<class 'pybind11_builtins.pybind11_type'>
Run Code Online (Sandbox Code Playgroud)
因此,方法是创建一个新的元类,例如pybind11_ext_enum,它派生自pybind11_type,并提供缺失的__len__。
下一个问题是,我们如何从 C++ 创建这样的元类。Pybind11 没有提供任何方便的功能来执行此操作,因此我们必须自己完成。为此,我们需要:
代表原始 pybind11 元类的对象pybind11_type。我发现它藏在里面internals,所以我从那里抓住了它。
py::object get_pybind11_metaclass()
{
auto &internals = py::detail::get_internals();
return py::reinterpret_borrow<py::object>((PyObject*)internals.default_metaclass);
}
Run Code Online (Sandbox Code Playgroud)
type代表标准 Python 元类(即在 CPython API 中)的对象PyType_Type。
py::object get_standard_metaclass()
{
auto &internals = py::detail::get_internals();
return py::reinterpret_borrow<py::object>((PyObject *)&PyType_Type);
}
Run Code Online (Sandbox Code Playgroud)
我们希望这个新类具有的属性字典。这只需要一个条目来定义我们的__len__方法。
py::dict attributes;
attributes["__len__"] = py::cpp_function(
[](py::object cls) {
return py::len(cls.attr("__entries"));
}
, py::is_method(py::none())
);
Run Code Online (Sandbox Code Playgroud)
用于type创建我们的新类对象。
auto pybind11_metaclass = get_pybind11_metaclass();
auto standard_metaclass = get_standard_metaclass();
return standard_metaclass(std::string("pybind11_ext_enum")
, py::make_tuple(pybind11_metaclass)
, attributes);
Run Code Online (Sandbox Code Playgroud)
我们可以将第 3 部分和第 4 部分放入一个函数中:py::object create_enum_metaclass() { ... }。
最后,我们在创建枚举包装器时必须使用新的元类。
>>> print(Kind.__entries)
{'Kind1': (Kind.Kind1, None), 'Kind2': (Kind.Kind2, None), 'Kind3': (Kind.Kind3, None)}
Run Code Online (Sandbox Code Playgroud)
现在我们可以在 Python 中使用它:
>>> from so07 import Kind
>>> type(Kind)
<class 'importlib._bootstrap.pybind11_ext_enum'>
>>> len(Kind)
3
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
320 次 |
| 最近记录: |