import classes from pyside inside of a boost pytho

2019-04-15 19:52发布

问题:

I'd like to use PySide to define the basic QT classes and the mapping between C++ and python, but to do so in both standalone python code and from embedded python using boost::python.

First, the module definition and class returning QPointF:

QPointF  X::getY() { 
  return QPointF(); 
}    

BOOST_PYTHON_MODULE(myBoostPythonModule)
{
// is there some magic init/register commands to put here?
boost::python::api::object module     = import("__main__");
boost::python::api::object name_space = module.attr("__dict__");
boost::python::exec("from PySide.QtCore import *",name_space,name_space);
boost::python::exec("import sys,os\nprint(sys.modules)",name_space,name_space);

class_<X, boost::noncopyable>(
            "X", init<const char*>())
        .def("getY",&X::getY)
        ;
}

Now, the application's embedded python code, last line is what fails and I'm wondering how to get around:

execute("import myBoostPythonModule");     // OK
execute("x=myBoostPythonModule.X('foo')"); // OK

execute("w=QPointF()\nprint(w)");          // OK
// PySide.QtCore.QPointF(0.000000, 0.000000)

execute("y=x.getY()");                     // FAIL:
// TypeError: No to_python (by-value) converter found for C++ type: QPointF

What's going on here, I can create a QPointF, but the name somehow isn't bound between python and c++ ? Am I missing some imports in the module to tell it to import from PySide?

回答1:

PySide provides its Qt bindings with Shiboken. Shiboken generates Python C API bindings that supporting its own type conversion system. The knowledge of these conversions resides within the Shiboken generated bindings, and not the Python type system. Thus, PySide knows how to convert a QPointF object to/from C++/Python; Python's type system does not.

When an object transitions through a function exposed with Boost.Python, then Boost.Python will check its registry for the appropriate type converter. These converters provide Boost.Python with knowledge on how to convert to/from C++/Python for types exposed through Boost.Python. Hence, when Boost.Python tries to return a QPointF C++ type to Python, it throws an exception as the conversion has not been registered with Boost.Python.

Here is the annotated code:

import myBoostPythonModule
from PySide.QtCore import *
...
x=myBoostPythonModule.X('foo') # Boost.Python knows how to convert C++ X
                               # to Python X.  Python's type system does not.

w=QPointF()                    # Shiboken knows how to convert C++ QPointF to
                               # Python QPointF.  Python's type system does not.
print(w)                       # Shiboken knows how to represent C++ QPointF as
                               # a string.

y=x.getY()                     # Boost.Python knows how to invoke X::getY(),
                               # but only Shiboken knows how to convert C++
                               # QPointF to Python QPointF.  Thus, the TypeError
                               # exception is raised.

It is possible to implement Boost.Python's converters in terms of another implementation. Expanding upon the Shiboken type converter example, below is a complete example of Boost.Python's converters implemented with Shiboken's old type converter. I would have used the new Shiboken type converter API, but it was unclear to me as to what it is based on the documentation.

#include <iostream>

#include <boost/python.hpp>

/// @brief Mockup Complex class from Shiboken documentation.
class Complex
{
public:
  Complex(double real, double imaginary)
    : real_(real),
      imaginary_(imaginary)
  {}

  double real() const      { return real_;      }
  double imaginary() const { return imaginary_; }

private:
  double real_;
  double imaginary_;
};

/// @brief Mocked up Shiboken converter.
namespace Shiboken {

template <typename> struct Converter;

template <> struct Converter<Complex>
{
public:
  // ...

  static inline bool isConvertible(PyObject* pyObj)
  {
    std::cout << "Shiboken::Converter<Complex>::isConvertible()" << std::endl;
    return PyComplex_Check(pyObj);
  }

  // ...

  static inline PyObject* toPython(const Complex& cpx)
  {
    std::cout << "Shiboken::Converter<Complex>::toPython()" << std::endl;
    return PyComplex_FromDoubles(cpx.real(), cpx.imaginary());
  }

  static inline Complex toCpp(PyObject* pyobj)
  {
    std::cout << "Shiboken::Converter<Complex>::toCpp()" << std::endl;
    double real      =  PyComplex_RealAsDouble(pyobj);
    double imaginary =  PyComplex_ImagAsDouble(pyobj);
    return Complex(real, imaginary);
  }
};
} // namespace Shiboken

/// @brief Type used to convert a complex to Python.
struct complex_converter_to_python
{
  static PyObject* convert(const Complex& c)
  {
    // Delegate to Shiboken.
    std::cout << "complex_converter_to_python::convert()" << std::endl;
    return Shiboken::Converter<Complex>::toPython(c);
  }
};

/// @brief Type that registers a Python Complex type to C++
///        Complex when passing through Boost.Python.
struct complex_converter_from_python
{
  /// @note Registers converter from a python complex to C++ complex.
  complex_converter_from_python()
  {
    boost::python::converter::registry::push_back(
      &complex_converter_from_python::convertible,
      &complex_converter_from_python::construct,
      boost::python::type_id<Complex>());
  }

  /// @brief Check if PyObject is a Complex.
  static void* convertible(PyObject* object)
  {
    // Delegate to Shiboken.  Based on the documentation, the
    // isConvertible function is gone, so explicit checking may
    // be required based on the version of Shiboken.
    std::cout << "complex_converter_from_python::convertible()" << std::endl;
    return Shiboken::Converter<Complex>::isConvertible(object)
      ? object 
      : NULL;
  }

  /// @brief Convert Python Complex to C++ Complex.
  static void construct(
    PyObject* object,
    boost::python::converter::rvalue_from_python_stage1_data* data)
  {
    // Obtain a handle to the memory block that the Boost.Python
    // converter has allocated for the C++ type.
    namespace python = boost::python;
    typedef python::converter::rvalue_from_python_storage<Complex>
                                                                storage_type;
    void* storage = reinterpret_cast<storage_type*>(data)->storage.bytes;

    // In-place construct a Complex type via copy-constructor, passing
    // in a Complex created from Shiboken.
    std::cout << "complex_converter_from_python::construct()" << std::endl;
    data->convertible = new (storage) Complex(
      Shiboken::Converter<Complex>::toCpp(object));
  }
};

/// @brief Factory function used to exercise to-python conversion.
Complex make_complex(double real, double imaginary)
{
  return Complex(real, imaginary);
}

/// @brief Printing function used to exercise from-python converison.
void print_complex(const Complex& c)
{
  std::cout << "In print_complex: "
            << c.real() << ", "
            << c.imaginary() << std::endl;
}

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;

  // Register Complex from python converter.
  complex_converter_from_python();

  // Register Complex to python converter.
  python::to_python_converter<
    Complex,
    complex_converter_to_python>();

  python::def("make_complex",  &make_complex);
  python::def("print_complex", &print_complex);
}

And its usage:

>>> import example
>>> x = example.make_complex(4, 2)
complex_converter_to_python::convert()
Shiboken::Converter<Complex>::toPython()
>>> example.print_complex(x)
complex_converter_from_python::convertible()
Shiboken::Converter<Complex>::isConvertible()
complex_converter_from_python::construct()
Shiboken::Converter<Complex>::toCpp()
In print_complex: 4, 2

Alternative, while not the most elegant of approaches, one could have the functions exposed from Boost.Python use boost::python::object types, and interface with the object via Python statements. Something similar to:

boost::python::object X::getY()
{ 
  return boost::python::exec("QPointF()", ...); 
}

The above code will have Python instantiate a QPointF Python object, which will delegate to Shiboken's type system. As X::getY() returns a generic object, Boost.Python will not attempt to perform type conversions when the object handle transitions from C++ to Python.



回答2:

Based on the earlier replies and other info I've found, here's a somewhat general routine to allow things like PySide.QtGui.QColor argument to get passed into a boost::python wrapped c++ method that expects a QColor& input argument:

template<class QtGuiClass,int SBK_BOGAN_IDX>
struct QtGui_from_python {
QtGui_from_python() {
  qDebug()<<" registering type: " << typeid(this).name() << " in " << __FUNCTION__;
  boost::python::converter::registry::push_back(
    &convertible,
    &construct,
    boost::python::type_id<QtGuiClass>());
}
static void* convertible(PyObject* obj_ptr) {
    if(!PyObject_TypeCheck(obj_ptr,
            (PyTypeObject*)XSbkPySide_QtGuiTypes[SBK_BOGAN_IDX]))
    {    qDebug()<<"Failed type check!?"; }
    return obj_ptr;
}
static void construct( PyObject* obj_ptr, 
   bp::converter::rvalue_from_python_stage1_data* data)
{
    void* storage = (
    (boost::python::converter::rvalue_from_python_storage<QtGuiClass>*)
    data)->storage.bytes;

    SbkObject* result = reinterpret_cast<SbkObject*>(obj_ptr);
    auto _ptr = (QtGuiClass*) (Shiboken::Object::cppPointer(
                                       result,Py_TYPE(result)));
    new (storage) QtGuiClass(*_ptr);
    qDebug() << "__alloc'd " << typeid(*_ptr).name() <<" at "<<_ptr;
    data->convertible = storage;
}
};

and, calling the above from a startup function:

QtGui_from_python<QColor,SBK_QCOLOR_IDX>();