Using pybind11
I wrap a C lib that I cannot modify. An issue came from a class that derives from std::vector
. (Notes: This my first pybind11
project. I am not 'fluent' in Python. I was looking for solution on the web but without success.)
Intro. Instance of E
carries error data. E
could be instantiated only by Es
- a collector. Es
gets enlarged with new instances of E
by method(s) like addFront(...)
while returning back from failed method(s) (i.e unwinding the call stack).
Minimalistic source code:
#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;
}
};
Since derived from std::vector
, as I learned, a type_caster
for Es
should be applied
so it can be used as a list
on Python side:
namespace pybind11 { namespace detail {
template <> struct type_caster< Es > : list_caster< Es, E > {};
}} // namespace pybind11::detail
The pybind11
part is:
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 );
}
When this Python code is executed:
from AWB import ID, E, Es
es = Es( ID.wrongParam )
es.addFront( ID.unknown )
python complains:
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>
>>>
Q: What I am doing wrong?
Q: Why arg0: AWB.ID
is incompatible with ID.unknown
?
Q: Maybe the type casting should be more precise?
Well, in real world, I don't expect the Es
will enlarge on Pyhon side. Mostly, I would need to export the collection (in human readble manner) what the Es
has collected so far (on the C side). But since I am writting a test case - I need to be sure it works.
Q: Even if it is possible, to use addFront
, on Python side, to add an item into C std::vector
... Would it be automatically visible in the Python's list
?
CodePudding user response:
Since you want to use C member functions specific to Es
, I think you shouldn't try to use type-casting in this case. If you would type-cast Es
, that means your Python type will be a copied list of E
objects but it wouldn't have methods like addFront
- you'll have append
etc.
What you can do is to wrap your type as an opaque type, and export the methods you need. This example is from the pybind11 documentation:
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 */
// ....
CodePudding user response:
According to unddoch answer, I slightly reworked the code. Here are the changes that might be of help for others.
First, replace the type_caster
block of code with:
PYBIND11_MAKE_OPAQUE(Es);
and if binding instructions for py::class_<Es>(m, "Es")
are enlarged with:
.def("__len__", [](const Es& v) {return v.size();})
the Python sequence executes as:
>>> from AWB import ID, E, Es
>>> es = Es(ID.wrongParam)
>>> es = es.addFront(ID.unknown)
>>> len(es)
2