User Defined Type Conversion¶
In the process of creating Python bindings of a C++ library, most of the C++ classes will have wrappers representing them in Python land. But there may be other classes that are very simple and/or have a Python type as a direct counter part. (Example: a “Complex” class, that represents complex numbers, has a Python equivalent in the “complex” type.) Such classes, instead of getting a Python wrapper, normally have conversions rules, from Python to C++ and vice-versa.
// C++ class struct Complex { Complex(double real, double imag); double real() const; double imag() const; }; // Converting from C++ to Python using the CPython API: PyObject* pyCpxObj = PyComplex_FromDoubles(complex.real(), complex.imag()); // Converting from Python to C++: double real = PyComplex_RealAsDouble(pyCpxObj); double imag = PyComplex_ImagAsDouble(pyCpxObj); Complex cpx(real, imag);
For the user defined conversion code to be inserted in the proper places, the “<conversion-rule>” tag must be used.
<primitive-type name="Complex" target-lang-api-name="PyComplex"> <include file-name="complex.h" location="global"/> <conversion-rule> <native-to-target> return PyComplex_FromDoubles(%in.real(), %in.imag()); </native-to-target> <target-to-native> <!-- The 'check' attribute can be derived from the 'type' attribute, it is defined here to test the CHECKTYPE type system variable. --> <add-conversion type="PyComplex" check="%CHECKTYPE[Complex](%in)"> double real = PyComplex_RealAsDouble(%in); double imag = PyComplex_ImagAsDouble(%in); %out = %OUTTYPE(real, imag); </add-conversion> </target-to-native> </conversion-rule> </primitive-type>
The details will be given later, but the gist of it are the tags native-to-target, which has only one conversion from C++ to Python, and native-to-native, that may define the conversion of multiple Python types to C++’s “Complex” type.
Shiboken expects the code for native-to-target, to directly return the Python result of the conversion, and the added conversions inside the target-to-native must attribute the Python to C++ conversion result to the %out variable.
Expanding on the last example, if the binding developer want a Python 2-tuple of numbers to be accepted by wrapped C++ functions with “Complex” arguments, an add-conversion tag and a custom check must be added. Here’s how to do it:
<!-- Code injection at module level. --> <inject-code class="native" position="beginning"> static bool Check2TupleOfNumbers(PyObject* pyIn) { if (!PySequence_Check(pyIn) || !(PySequence_Size(pyIn) == 2)) return false; Shiboken::AutoDecRef pyReal(PySequence_GetItem(pyIn, 0)); if (!SbkNumber_Check(pyReal)) return false; Shiboken::AutoDecRef pyImag(PySequence_GetItem(pyIn, 1)); if (!SbkNumber_Check(pyImag)) return false; return true; } </inject-code> <primitive-type name="Complex" target-lang-api-name="PyComplex"> <include file-name="complex.h" location="global"/> <conversion-rule> <native-to-target> return PyComplex_FromDoubles(%in.real(), %in.imag()); </native-to-target> <target-to-native> <add-conversion type="PyComplex"> double real = PyComplex_RealAsDouble(%in); double imag = PyComplex_ImagAsDouble(%in); %out = %OUTTYPE(real, imag); </add-conversion> <add-conversion type="PySequence" check="Check2TupleOfNumbers(%in)"> Shiboken::AutoDecRef pyReal(PySequence_GetItem(%in, 0)); Shiboken::AutoDecRef pyImag(PySequence_GetItem(%in, 1)); double real = %CONVERTTOCPP[double](pyReal); double imag = %CONVERTTOCPP[double](pyImag); %out = %OUTTYPE(real, imag); </add-conversion> </target-to-native> </conversion-rule> </primitive-type>
Container Conversions¶
Converters for container-type are pretty much the same as for other type, except that they make use of the type system variables %INTYPE_# and %OUTTYPE_#. Shiboken combines the conversion code for containers with the conversion defined (or automatically generated) for the containers.
<container-type name="std::map" type="map"> <include file-name="map" location="global"/> <conversion-rule> <native-to-target> PyObject* %out = PyDict_New(); %INTYPE::const_iterator it = %in.begin(); for (; it != %in.end(); ++it) { %INTYPE_0 key = it->first; %INTYPE_1 value = it->second; PyDict_SetItem(%out, %CONVERTTOPYTHON[%INTYPE_0](key), %CONVERTTOPYTHON[%INTYPE_1](value)); } return %out; </native-to-target> <target-to-native> <add-conversion type="PyDict"> PyObject* key; PyObject* value; Py_ssize_t pos = 0; while (PyDict_Next(%in, &pos, &key, &value)) { %OUTTYPE_0 cppKey = %CONVERTTOCPP[%OUTTYPE_0](key); %OUTTYPE_1 cppValue = %CONVERTTOCPP[%OUTTYPE_1](value); %out.insert(%OUTTYPE::value_type(cppKey, cppValue)); } </add-conversion> </target-to-native> </conversion-rule> </container-type>
Variables & Functions¶
%in
Variable replaced by the C++ input variable.
%out
Variable replaced by the C++ output variable. Needed to convey the result of a Python to C++ conversion.
%INTYPE
Used in Python to C++ conversions. It is replaced by the name of type for which the conversion is being defined. Don’t use the type’s name directly.
%INTYPE_#
Replaced by the name of the #th type used in a container.
%OUTTYPE
Used in Python to C++ conversions. It is replaced by the name of type for which the conversion is being defined. Don’t use the type’s name directly.
%OUTTYPE_#
Replaced by the name of the #th type used in a container.
%CHECKTYPE[CPPTYPE]
Replaced by a Shiboken type checking function for a Python variable. The C++ type is indicated by
CPPTYPE
.
Converting The Old Converters¶
If you use Shiboken for your bindings, and has defined some type conversions
using the Shiboken::Converter
template, then you must update your converters
to the new scheme.
Previously your conversion rules were declared in one line, like this:
<primitive-type name="Complex" target-lang-api-name="PyComplex"> <include file-name="complex.h" location="global"/> <conversion-rule file="complex_conversions.h"/> </primitive-type>
And implemented in a separate C++ file, like this:
namespace Shiboken { template<> struct Converter<Complex> { static inline bool checkType(PyObject* pyObj) { return PyComplex_Check(pyObj); } static inline bool isConvertible(PyObject* pyObj) { return PyComplex_Check(pyObj); } static inline PyObject* toPython(void* cppobj) { return toPython(*reinterpret_cast<Complex*>(cppobj)); } static inline PyObject* toPython(const Complex& cpx) { return PyComplex_FromDoubles(cpx.real(), cpx.imag()); } static inline Complex toCpp(PyObject* pyobj) { double real = PyComplex_RealAsDouble(pyobj); double imag = PyComplex_ImagAsDouble(pyobj); return Complex(real, imag); } }; }
In this case, the parts of the implementation that will be used in the new
conversion-rule are the ones in the two last method
static inline PyObject* toPython(const Complex& cpx)
and
static inline Complex toCpp(PyObject* pyobj)
. The isConvertible
method
is gone, and the checkType
is now an attribute of the add-conversion
tag. Refer back to the first example in this page and you will be able to
correlate the above template with the new scheme of conversion rule definition.
© 2022 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.