Search code examples
pythonc++boostboost-pythonpython-extensions

How can i declare a Boost.Python C++ Class with PyObjects


i want to write a c++ class with PyObjects to access them from Python to reference them with an existing Python object instance. In short words i want to store/manage Python object instances in the C++ class.

For example:

struct Var
{
    PyObject *Test_1;
    PyObject *Test_2;

};

#include <boost/python.hpp>
using namespace boost::python;

BOOST_PYTHON_MODULE(Var)
{
    class_<Var>("Var", init<std::string>())
        .def_readwrite("Test_1", &Var::value)
        .def_readwrite("Test_2", &Var::value)
    ;
}

in python i want to do this if it is possible with Boost.Python:

class Test:
    def __init__(self, a = 0, b = 2):
        self.a = 0
        self.b = 0


test = Test(2,2)

import Var
newStore = Var
newStore.Test_1 = test

Thank you a lot in advance !

greets

Flo


Solution

  • When managing Python objects in C++ with Boost.Python, one should consider using the boost::python::object class rather than PyObject. The object acts very much like a Python variables, allowing for Python-ish code in C++. Furthermore, they behave similar to a smart pointer, providing referencing counting and lifetime management, where as one would need to explicitly manage the reference count with a PyObject.


    Here is a complete example based on the original code that demonstrates using boost::python::object and PyObject:

    #include <boost/python.hpp>
    
    /// @brief Mockup type that can manage two Python objects.
    struct var
    {
      boost::python::object test_1; // managed
      PyObject* test_2;             // must explicitly manage
    
      var()
        : test_2(Py_None)
      {
        Py_INCREF(test_2);
      }
    
      ~var()
      {
        Py_DECREF(test_2);
      }
    };
    
    /// @brief Auxiliary function used to return a non-borrowed reference to
    //         self.test_2.  This is necessary because Boost.Python assumes
    //         that PyObject* passed from C++ to Python are not borrowed.
    PyObject* var_test_2_getter(const var& self)
    {
      PyObject* object = self.test_2;
      Py_INCREF(object);
      return object;
    }
    
    /// @brief Auxiliary function used to manage the reference count of
    ///        objects assigned to var.test_2.
    void var_test_2_setter(var& self, PyObject* object)
    {
      Py_DECREF(self.test_2);
      self.test_2 = object;
      Py_INCREF(self.test_2);
    }
    
    BOOST_PYTHON_MODULE(example)
    {
      namespace python = boost::python;
      python::class_<var>("Var", python::init<>())
        .def_readwrite("Test_1", &var::test_1)
        .add_property("Test_2", &var_test_2_getter, &var_test_2_setter)
        ;
    }
    

    Interactive usage:

    >>> class Test:
    ...     def __init__(self, a=0, b=2):
    ...         self.a = a
    ...         self.b = b
    ... 
    >>> test = Test(2, 2)
    >>> from sys import getrefcount
    >>> count = getrefcount(test)
    >>> import example
    >>> store = example.Var()
    >>> store.Test_1 = test
    >>> assert(store.Test_1 is test)
    >>> assert(count + 1 == getrefcount(test))
    >>> assert(store.Test_1.a == 2)
    >>> store.Test_1.a = 42
    >>> assert(test.a == 42)
    >>> store.Test_2 = test
    >>> assert(store.Test_2 is test)
    >>> assert(count + 2 == getrefcount(test))
    >>> assert(count + 2 == getrefcount(store.Test_2))
    >>> store.Test_2 = None
    >>> assert(count + 1 == getrefcount(test))
    >>> store = None
    >>> assert(count == getrefcount(test))