为什么 Python 在调用派生自 C++ std::vector 的 pybind11 type_cast-ed class 方法时会报错?
Why Python complains when called method of pybind11 type_cast-ed class that derives from C++ std::vector?
使用 pybind11
我包装了一个我无法修改的 C++ 库。问题来自 std::vector
派生的 class。 (注:这是我的第一个 pybind11
项目。我不是 Python 中的 'fluent'。我在网上寻找解决方案但没有成功。)
简介。 E
的实例携带错误数据。 E
只能由 Es
实例化 - 一个收集器。 Es
通过 addFront(...)
等方法用 E
的新实例扩大,同时从失败的方法返回(即展开调用堆栈)。
极简源代码:
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
namespace py = pybind11;
using namespace pybind11::literals;
// Classes
enum class ID { unknown, wrongParam };
class E {
public:
ID GetID() const { return id; }
protected:
E( ID _id ) { id = _id; };
ID id;
friend class Es;
};
class Es : public std::vector< E > {
public:
Es() {}
Es( ID _id ) { push_back( E( _id ) ); }
Es& addFront( ID _id ) {
insert( begin(), E( _id ) ); // Base class methods!
return *this;
}
};
由于从 std::vector
派生,据我了解,应该应用 Es
的 type_caster
所以它可以用作 Python 侧的 list
:
namespace pybind11 { namespace detail {
template <> struct type_caster< Es > : list_caster< Es, E > {};
}} // namespace pybind11::detail
pybind11
部分是:
void Bind( py::module_& m ) {
py::enum_< ID >( m, "ID" )
.value( "unknown", ID::unknown )
.value( "wrongParam", ID::wrongParam );
py::class_< E >( m, "E" )
.def( "GetID", &E::GetID );
py::class_< Es >( m, "Es" )
.def( py::init<>() )
.def( py::init< ID >(), "id"_a )
.def( "addFront", &Es::addFront );
}
当执行此 Python 代码时:
from AWB import ID, E, Es
es = Es( ID.wrongParam )
es.addFront( ID.unknown )
python抱怨:
E:\Projects\AWB>py
Python 3.8.10 (tags/v3.8.10:3d8993a, May 3 2021, 11:48:03) [MSC v.1928 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from AWB import ID, E Es
>>> es = Es( ID.wrongParam )
>>> es.addFront( ID.unknown )
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: addFront(): incompatible function arguments. The following argument types are supported:
1. (self: List[AWB.E], arg0: AWB.ID) -> List[AWB.E]
Invoked with: <AWB.Es object at 0x000000000260CEB0>, <ID.unknown: 0>
>>>
问:我做错了什么?
问:为什么 arg0: AWB.ID
与 ID.unknown
不兼容?
问:也许 类型转换 应该更精确?
好吧,在现实世界中,我不希望 Es
会在 Pyhon 端扩大。大多数情况下,我需要导出集合(以人类可读的方式)Es
到目前为止(在 C++ 端)收集的内容。但由于我正在编写测试用例 - 我需要确保它有效。
问:即使有可能,在 Python 端使用 addFront
将项目添加到 C++ std::vector
... 是否会自动 在 Python 的 list
?
中可见
既然您想使用特定于 Es
的 C++ 成员函数,我认为您不应该在这种情况下尝试使用类型转换。如果你要类型转换 Es
,这意味着你的 Python 类型将是 E
对象的复制列表,但它不会有像 addFront
这样的方法 - 你会有append
等
您可以做的是包装您的类型 as an opaque type,并导出您需要的方法。此示例来自 pybind11 文档:
py::class_<std::vector<int>>(m, "IntVector")
.def(py::init<>())
.def("clear", &std::vector<int>::clear)
.def("pop_back", &std::vector<int>::pop_back)
.def("__len__", [](const std::vector<int> &v) { return v.size(); })
.def("__iter__", [](std::vector<int> &v) {
return py::make_iterator(v.begin(), v.end());
}, py::keep_alive<0, 1>()) /* Keep vector alive while iterator is used */
// ....
根据,我稍微修改了代码。以下是可能对其他人有帮助的更改。
首先,将 type_caster
代码块替换为:
PYBIND11_MAKE_OPAQUE(Es);
如果 py::class_<Es>(m, "Es")
的绑定说明被放大为:
.def("__len__", [](const Es& v) {return v.size();})
Python 序列执行为:
>>> from AWB import ID, E, Es
>>> es = Es(ID.wrongParam)
>>> es = es.addFront(ID.unknown)
>>> len(es)
2
使用 pybind11
我包装了一个我无法修改的 C++ 库。问题来自 std::vector
派生的 class。 (注:这是我的第一个 pybind11
项目。我不是 Python 中的 'fluent'。我在网上寻找解决方案但没有成功。)
简介。 E
的实例携带错误数据。 E
只能由 Es
实例化 - 一个收集器。 Es
通过 addFront(...)
等方法用 E
的新实例扩大,同时从失败的方法返回(即展开调用堆栈)。
极简源代码:
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
namespace py = pybind11;
using namespace pybind11::literals;
// Classes
enum class ID { unknown, wrongParam };
class E {
public:
ID GetID() const { return id; }
protected:
E( ID _id ) { id = _id; };
ID id;
friend class Es;
};
class Es : public std::vector< E > {
public:
Es() {}
Es( ID _id ) { push_back( E( _id ) ); }
Es& addFront( ID _id ) {
insert( begin(), E( _id ) ); // Base class methods!
return *this;
}
};
由于从 std::vector
派生,据我了解,应该应用 Es
的 type_caster
所以它可以用作 Python 侧的 list
:
namespace pybind11 { namespace detail {
template <> struct type_caster< Es > : list_caster< Es, E > {};
}} // namespace pybind11::detail
pybind11
部分是:
void Bind( py::module_& m ) {
py::enum_< ID >( m, "ID" )
.value( "unknown", ID::unknown )
.value( "wrongParam", ID::wrongParam );
py::class_< E >( m, "E" )
.def( "GetID", &E::GetID );
py::class_< Es >( m, "Es" )
.def( py::init<>() )
.def( py::init< ID >(), "id"_a )
.def( "addFront", &Es::addFront );
}
当执行此 Python 代码时:
from AWB import ID, E, Es
es = Es( ID.wrongParam )
es.addFront( ID.unknown )
python抱怨:
E:\Projects\AWB>py
Python 3.8.10 (tags/v3.8.10:3d8993a, May 3 2021, 11:48:03) [MSC v.1928 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from AWB import ID, E Es
>>> es = Es( ID.wrongParam )
>>> es.addFront( ID.unknown )
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: addFront(): incompatible function arguments. The following argument types are supported:
1. (self: List[AWB.E], arg0: AWB.ID) -> List[AWB.E]
Invoked with: <AWB.Es object at 0x000000000260CEB0>, <ID.unknown: 0>
>>>
问:我做错了什么?
问:为什么 arg0: AWB.ID
与 ID.unknown
不兼容?
问:也许 类型转换 应该更精确?
好吧,在现实世界中,我不希望 Es
会在 Pyhon 端扩大。大多数情况下,我需要导出集合(以人类可读的方式)Es
到目前为止(在 C++ 端)收集的内容。但由于我正在编写测试用例 - 我需要确保它有效。
问:即使有可能,在 Python 端使用 addFront
将项目添加到 C++ std::vector
... 是否会自动 在 Python 的 list
?
既然您想使用特定于 Es
的 C++ 成员函数,我认为您不应该在这种情况下尝试使用类型转换。如果你要类型转换 Es
,这意味着你的 Python 类型将是 E
对象的复制列表,但它不会有像 addFront
这样的方法 - 你会有append
等
您可以做的是包装您的类型 as an opaque type,并导出您需要的方法。此示例来自 pybind11 文档:
py::class_<std::vector<int>>(m, "IntVector")
.def(py::init<>())
.def("clear", &std::vector<int>::clear)
.def("pop_back", &std::vector<int>::pop_back)
.def("__len__", [](const std::vector<int> &v) { return v.size(); })
.def("__iter__", [](std::vector<int> &v) {
return py::make_iterator(v.begin(), v.end());
}, py::keep_alive<0, 1>()) /* Keep vector alive while iterator is used */
// ....
根据
首先,将 type_caster
代码块替换为:
PYBIND11_MAKE_OPAQUE(Es);
如果 py::class_<Es>(m, "Es")
的绑定说明被放大为:
.def("__len__", [](const Es& v) {return v.size();})
Python 序列执行为:
>>> from AWB import ID, E, Es
>>> es = Es(ID.wrongParam)
>>> es = es.addFront(ID.unknown)
>>> len(es)
2