3

I'm looking to find out if there's a nice, "native" way to construct an object given a PyObject* that is known to be a type.

Here is my code as it stands:

C++

void add_component(boost::python::object& type)
{
    auto constructed_type = type(); // doesn't construct anything!
}

Python

o = GameObject()
o.add_component(CameraComponent)

My code is executing the entire function perfectly fine, but the constructor is never triggered for CameraComponent.

So my question is, how do I, given a PyObject* that is known to a be a type, construct an instance of that type?

Many thanks in advance.

3
  • 1
    If by constructor you mean that CameraComponent is a Python class and it's __init__ method should be called, then that works fine for me, with boost 1.54. If you mean something different, please clarify. (If GameObject wraps a C++ class, did you read the "Constructors" part of the documentation?) Commented Feb 5, 2016 at 10:06
  • 4
    Can you please post a minimal reproducible example? I was not able to duplicate the problem (see demo). Commented Feb 5, 2016 at 15:23
  • Turns it out was a problem of the compiler optimizing the call away. Commented Feb 6, 2016 at 2:44

2 Answers 2

2
+50

If a boost::python::object references a type, then invoking it will construct an object with the referenced type:

boost::python::object type = /* Py_TYPE */;
boost::python::object object = type(); // isinstance(object, type) == True

As nearly everything in Python is an object, accepting arguments from Python as boost::python::object will allow any type of object, even those that are not a type. As long as the object is callable (__call___), then the code will succeed.


On the other hand, if you want to guarantee that a type is provided, then one solution is to create a C++ type that represents a Python type, accept it as an argument, and use a custom converter to construct the C++ type only if a Python type is provided.

The following type_object C++ type represents a Python object that is a Py_TYPE.

/// @brief boost::python::object that refers to a type.
struct type_object: 
  public boost::python::object
{
  /// @brief If the object is a type, then refer to it.  Otherwise,
  ///        refer to the instance's type.
  explicit
  type_object(boost::python::object object):
    boost::python::object(object)
  {
    if (!PyType_Check(object.ptr()))
    {
      throw std::invalid_argument("type_object requires a Python type");
    }
  }
};

...

// Only accepts a Python type.
void add_component(type_object type) { ... }

The following custom converter will only construct a type_object instance if it is provided a PyObject* that is a Py_TYPE:

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

  static void* convertible(PyObject* object)
  {
    return PyType_Check(object) ? object : NULL;
  }

  static void construct(
    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;
  }
};

...

BOOST_PYTHON_MODULE(...)
{
  enable_type_object(); // register type_object converter.
}

Here is a complete example demonstrating exposing a function that requires a Python type, then constructs an instance of the type:

#include <iostream>
#include <stdexcept> // std::invalid_argument
#include <boost/python.hpp>

/// @brief boost::python::object that refers to a type.
struct type_object: 
  public boost::python::object
{
  /// @brief If the object is a type, then refer to it.  Otherwise,
  ///        refer to the instance's type.
  explicit
  type_object(boost::python::object object):
    boost::python::object(object)
  {
    if (!PyType_Check(object.ptr()))
    {
      throw std::invalid_argument("type_object requires a Python type");
    }
  }
};

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

  static void* convertible(PyObject* object)
  {
    return PyType_Check(object) ? object : NULL;
  }

  static void construct(
    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;
  }
};

// Mock API.
struct GameObject {};
struct CameraComponent
{
  CameraComponent()
  {
    std::cout << "CameraComponent()" << std::endl;
  }
};

boost::python::object add_component(GameObject& /* self */, type_object type)
{
  auto constructed_type = type();
  return constructed_type;
}

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

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

  python::class_<GameObject>("GameObject")
    .def("add_component", &add_component);

  python::class_<CameraComponent>("CameraComponent");
}

Interactive usage:

>>> import example
>>> game = example.GameObject()
>>> component = game.add_component(example.CameraComponent)
CameraComponent()
>>> assert(isinstance(component, example.CameraComponent))
>>> try:
...     game.add_component(component) # throws Boost.Python.ArgumentError
...     assert(False)
... except TypeError:
...     assert(True)
...
Sign up to request clarification or add additional context in comments.

Comments

0

This was actually always working, but the compiler was optimizing the constructor logic away somehow, so my breakpoints never got hit!

1 Comment

Compiler - "somehow" - optimizing. Compilers exist for a reason :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.