Custom Implicit Type Conversion
Target
I have define a c++ class named Data
, and I want it be converted to python object implicitly and vice versa. For example:
class Data
{
public:
Data(int val): _val{val} {}
int _val;
}
void py2cpp(const data& data)
{
cout << data._val << endl;
}
Data cpp2py()
{
return Data(41);
}
import example
example.py2cpp(42)
assert isinstance(example.cpp2py(), int)
Solution
We need to implement a type_caster to define how to convert c++ object to python object. type_caster
has two methods, a cast
to convert c++ to python and a load
to convert python to c++. Here is an example:
// def inty
struct inty { long long_value; };
namespace pybind11 { namespace detail {
template <> struct type_caster<inty> {
public:
/**
* This macro establishes the name 'inty' in
* function signatures and declares a local variable
* 'value' of type inty
*/
PYBIND11_TYPE_CASTER(inty, const_name("inty"));
/**
* Conversion part 1 (Python->C++): convert a PyObject into a inty
* instance or return false upon failure. The second argument
* indicates whether implicit conversions should be applied.
*/
bool load(handle src, bool) {
/* Extract PyObject from handle */
PyObject *source = src.ptr();
/* Try converting into a Python integer value */
PyObject *tmp = PyNumber_Long(source);
if (!tmp)
return false;
/* Now try to convert into a C++ int */
value.long_value = PyLong_AsLong(tmp);
Py_DECREF(tmp);
/* Ensure return code was OK (to avoid out-of-range errors etc) */
return !(value.long_value == -1 && !PyErr_Occurred());
}
/**
* Conversion part 2 (C++ -> Python): convert an inty instance into
* a Python object. The second and third arguments are used to
* indicate the return value policy and parent object (for
* ``return_value_policy::reference_internal``) and are generally
* ignored by implicit casters.
*/
static handle cast(inty src, return_value_policy /* policy */, handle /* parent */) {
return PyLong_FromLong(src.long_value);
}
};
}} // namespace PYBIND11_NAMESPACE::detail
The code is self-explanatory. In the load
method, we need to manually convert handle
to target c++ type and assign it to value
. If the conversion is success, return true, otherwise return false. In the cast
method, we need to convert c++ type to handle
object and return it. The PYBIND11_TYPE_CASTER
macro is used to define the name of the type caster and a local variable value
of type inty
.
As for test binding code, we can do like this:
PYBIND11_MODULE(example, m)
{
py::class_<inty>(m, "inty")
.def(py::init<int>())
.def_property("value", &inty::_val, &inty::_val);
m.def("py2cpp", [](const inty& data) {
std::cout << data._val << std::endl;
});
m.def("cpp2py", []() {
return inty(41);
});
}
Be careful! When you directly initilize a inty
object, you will get a python object with no convert.
>>> import example
>>> example.inty(42)
>>> <example.TypedData at 0x7f6c98aaa1b0> # not what you want
>>> example.cpp2py()
>>> 41
>>> example.py2cpp(42) # return nothing but print
If you want to see an actual code, you can check it out on cppdict.
It seems type_caster
only take effect locally
I have tried to define a type_caster
in another .cpp
file and link it with others. But it seems that the type_caster
only take effect in the cpp file where it is defined. Or you can define it in a header file and include it to the cpp file where you want to use it.
Reference
[1] pybind11
[2] cppdict