Search code examples
pythonc++boost-python

Boost.Python create handle from type


In some places in exposing C++ code to python, I need to use a PyObject*. If I have an instance of a boost::python::class_ object, I can invoke ptr() on it. But what if I just have the type?

Basically, given the type list boost::python::bases<A, B, C>, I want to convert this to a boost::python::tuple of instances that I can pass into something like PyErr_NewExceptionWithDoc(). Is this possible?


Solution

  • Given a C++ type T, one can create a boost::python::type_id object, and then query into the Boost.Python registry for registration information. If an entry is found in the registry, then one can use it to obtain a handle to the Python class created for type T:

    /// @brief Get the class object for a wrapped type that has been exposed
    ///        through Boost.Python.
    template <typename T>
    boost::python::object get_instance_class()
    {
      // Query into the registry for type T.
      namespace python = boost::python;
      python::type_info type = python::type_id<T>();
      const python::converter::registration* registration =
        python::converter::registry::query(type);
    
      // If the class is not registered, return None.
      if (!registration) return python::object();
    
      python::handle<PyTypeObject> handle(python::borrowed(
        registration->get_class_object()));
      return python::object(handle);
    }
    

    Here is a complete example demonstrating locating a Python class object in the Boost.Python registry:

    #include <boost/python.hpp>
    #include <iostream>
    
    /// @brief Get the class object for a wrapped type that has been exposed
    ///        through Boost.Python.
    template <typename T>
    boost::python::object get_instance_class()
    {
      // Query into the registry for type T.
      namespace python = boost::python;
      python::type_info type = python::type_id<T>();
      const python::converter::registration* registration =
        python::converter::registry::query(type);
    
      // If the class is not registered, return None.
      if (!registration) return python::object();
    
      python::handle<PyTypeObject> handle(python::borrowed(
        registration->get_class_object()));
      return python::object(handle);
    }
    
    struct spam {};
    
    int main()
    {
      Py_Initialize();
    
      namespace python = boost::python;
      try
      {
        // Create the __main__ module.
        python::object main_module = python::import("__main__");
        python::object main_namespace = main_module.attr("__dict__");
    
        // Create `Spam` class.
        // >>> class Spam: pass
        auto spam_class_object = python::class_<spam>("Spam", python::no_init);
        // >>> print Spam
        main_module.attr("__builtins__").attr("print")(get_instance_class<spam>());
        // >>> assert(spam is spam)
        assert(spam_class_object.ptr() == get_instance_class<spam>().ptr());
      }
      catch (python::error_already_set&)
      {
        PyErr_Print();
        return 1;
      }
    }
    

    Output:

    <class 'Spam'>
    

    For more type related functionality, such as accepting type objects, is, and issubclass, see this answer.