Skip to content Skip to sidebar Skip to footer

Pass A Type Object (class, Not An Instance) From Python To C++

I would like to have a boost::python-wrapped c++ function which is able to receive type (rather than an instance), a boost::python-wrapped c++ class. I can declare the wrapped func

Solution 1:

To pass a Python type object, one needs to create a C++ type and register a custom a custom converter. As a Python type object is a python object, creating a type that derives from boost::python::object is appropriate:

/// @brief boost::python::object that refers to a type.structtype_object: 
  public boost::python::object
{
  /// @brief If the object is a type, then refer to it.  Otherwise,///        refer to the instance's type.explicittype_object(boost::python::object object):
    boost::python::object(get_type(object))
  {}

private:

  /// @brief Get a type object from the given borrowed PyObject.static boost::python::object get_type(boost::python::object object){
    returnPyType_Check(object.ptr())
      ? object 
      : object.attr("__class__");
  }
};

// ... register custom converter for type_object.

However, the example code presents an additional problem. One cannot directly perform comparisons between a Python type object and a C++ type. Furthermore, The Python type object has no direct association with the C++ type. To perform comparisons, one needs to compare the Python type objects.

Boost.Python uses an internal registry to associate C++ type identity, in the form of boost::python::type_info, to a Python class object. This association is one-way, in that one can only lookup a Python class object. Lets expand the type_object class to allow to provide auxiliaries functions for checking against C++ types:

/// @brief boost::python::object that refers to a type.structtype_object: 
  public boost::python::object
{
  ...

  /// @brief Type identity check.  Returns true if this is the object returned///        returned from type() when passed an instance of an object created///        from a C++ object with type T.template <typename T>
  boolis()const{
    // Perform an identity check that registartion for type T and type_object// are the same Python type object.returnget_class_object<T>() == static_cast<void*>(ptr());
  }

  /// @brief Type identity check.  Returns true if this is the object is a///        subclass of the type returned returned from type() when passed///        an instance of an object created from a C++ object with type T.template <typename T>
  boolis_subclass()const{
    returnPyType_IsSubtype(reinterpret_cast<PyTypeObject*>(ptr()),
                            get_class_object<T>());
  }

private:

  ...

  /// @brief Get the Python class object for C++ type T.template <typename T>
  static PyTypeObject* get_class_object(){
    namespace python = boost::python;
    // Locate registration based on the C++ type.const python::converter::registration* registration =
          python::converter::registry::query(python::type_id<T>());

    // If registration exists, then return the class object.  Otherwise,// return NULL.return (registration) ? registration->get_class_object()
                          : NULL;
  }
};

Now, if type is an instance of type_object, one could check:

  • If type is the Python type associated with the C++ Spam type with type.is<Spam>().
  • If type is a subclass of the Python type associated with the C++ Spam type with type.is_subclass<Spam>().

Here is a complete example based on the original code that demonstrates receiving type objects to functions, checking for type identity and subclasses:

#include<boost/python.hpp>/// @brief boost::python::object that refers to a type.structtype_object: 
  public boost::python::object
{
  /// @brief If the object is a type, then refer to it.  Otherwise,///        refer to the instance's type.explicittype_object(boost::python::object object):
    boost::python::object(get_type(object))
  {}

  /// @brief Type identity check.  Returns true if this is the object returned///        returned from type() when passed an instance of an object created///        from a C++ object with type T.template <typename T>
  boolis()const{
    // Perform an identity check that registartion for type T and type_object// are the same Python type object.returnget_class_object<T>() == static_cast<void*>(ptr());
  }

  /// @brief Type identity check.  Returns true if this is the object is a///        subclass of the type returned returned from type() when passed///        an instance of an object created from a C++ object with type T.template <typename T>
  boolis_subclass()const{
    returnPyType_IsSubtype(reinterpret_cast<PyTypeObject*>(ptr()),
                            get_class_object<T>());
  }

private:

  /// @brief Get a type object from the given borrowed PyObject.static boost::python::object get_type(boost::python::object object){
    returnPyType_Check(object.ptr())
      ? object 
      : object.attr("__class__");
  }

  /// @brief Get the Python class object for C++ type T.template <typename T>
  static PyTypeObject* get_class_object(){
    namespace python = boost::python;
    // Locate registration based on the C++ type.const python::converter::registration* registration =
          python::converter::registry::query(python::type_id<T>());

    // If registration exists, then return the class object.  Otherwise,// return NULL.return (registration) ? registration->get_class_object()
                          : NULL;
  }
};

/// @brief Enable automatic conversions to type_object.structenable_type_object
{
  enable_type_object()
  {
    boost::python::converter::registry::push_back(
      &convertible,
      &construct,
      boost::python::type_id<type_object>());
  }

  staticvoid* convertible(PyObject* object){
    return (PyType_Check(object) || Py_TYPE(object)) ? object : NULL;
  }

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

    // Construct the type object within the storage.  Object is a borrowed // reference, so create a handle indicting it is borrowed for proper// reference counting.
    python::handle<> handle(python::borrowed(object));
    new (storage) type_object(python::object(handle));

    // Set convertible to indicate success. 
    data->convertible = storage;
  }
};

// Mockup types.structA {};
structB: public A {};
structC {};

/// Mockup function that receives an object's type.intfunc(type_object type){
  if (type.is<A>()) return0;
  if (type.is<B>()) return1;
  return-1;
}

/// Mockup function that returns true if the provided object type is a/// subclass of A.boolisSubclassA(type_object type){
  return type.is_subclass<A>();
}

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

  // Enable receiving type_object as arguments.enable_type_object();

  python::class_<A>("A");
  python::class_<B, python::bases<A> >("B");
  python::class_<C>("C");

  python::def("func", &func);
  python::def("isSubclassA", &isSubclassA);
}

Interactive usage:

>>> import example
>>> assert(example.func(type("test")) == -1)
>>> assert(example.func(example.A) == 0)
>>> assert(example.func(example.B) == 1)
>>> assert(example.isSubclassA(example.A))
>>> assert(example.isSubclassA(example.B))
>>> assert(not example.isSubclassA(example.C))
>>> assert(example.func("test") == -1)
>>> assert(example.func(example.A()) == 0)
>>> assert(example.func(example.B()) == 1)
>>> assert(example.isSubclassA(example.A()))
>>> assert(example.isSubclassA(example.B()))
>>> assert(not example.isSubclassA(example.C()))

Post a Comment for "Pass A Type Object (class, Not An Instance) From Python To C++"