Question

I have a C++ class which contains some kind of "static state" ("m_engine" in this particular case):

class RndGenerator
{
public:
  static void setInitialSeed(unsigned int seed);
  static unsigned int rndNumber();
  ...
private:
  ...
  RndGenerator();
  static std::mt19937 m_engine;
};

This class is used extensively in my project, on C++ level.

After exposing RndGenerator via Boost.Python:

class_<RndGenerator, boost::noncopyable>("RndGenerator", no_init)
  .def("setInitialSeed", &RndGenerator::setInitialSeed)
  .staticmethod("setInitialSeed")
  .def("rndNumber", &RndGenerator::rndNumber)
  .staticmethod("rndNumber")
  ;

I would like to have possibility to set initial seed from Python level:

 RndGenerator.setInitialSeed(1234)

I would expect, that after this line all calls to RndGenerator::rndNumber(), on C++ level, would take into account just specified initial seed (1234). However this is not the case.

Is there any problem with classes containing static members exposed to Python? Or is my problem related to singleton-like nature of RndGenerator?

Was it helpful?

Solution

There should be no problems with Boost.Python using static data members or static member functions on C++ classes being exposed. Is it possible that this is a false-positive? Alternatively, for more complex and specific cases where the same template is instantiated in multiple translation units, then with dynamic libraries, multiple instances of static data members with the same symbolic name may reside within the same process space.

Regardless, here is a complete example demonstrating the expected behavior of static member functions and static data members on a class exposed with Boost.Python:

#include <boost/python.hpp>

// Basic mockup type.
class spam
{
public:
  static void set_x(unsigned int x) { x_ = x; }
  static unsigned int get_x() { return x_; };
private:
  spam() {};
  spam(const spam&);
  spam& operator=(const spam&);
  static unsigned int x_;
};

unsigned int spam::x_ = 0;

// Auxiliary function.
bool spam_check_x(unsigned int x)
{
  return x == spam::get_x();
}

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::class_<spam, boost::noncopyable>("Spam", python::no_init)
    .def("setX", &spam::set_x)
      .staticmethod("setX")
    .def("getX", &spam::get_x)
      .staticmethod("getX")
    .def("checkX", &spam_check_x)
      .staticmethod("checkX")
    ;
}

Interactive usage:

>>> from example import Spam
>>> x = 42
>>> assert(Spam.getX() != x)
>>> assert(not Spam.checkX(x))
>>> Spam.setX(x)
>>> assert(Spam.getX() == x)
>>> assert(Spam.checkX(x))
>>> x = 43
>>> assert(Spam.getX() != x)
>>> assert(not Spam.checkX(x))

OTHER TIPS

I realize this is an old question, but I ran into this same issue recently and figured I should share.

We were able to reproduce the issue on Windows with Boost 1.56 with the following steps:

  • In C++, create two separate Boost.Python modules:
    • Module A exposes a function to set a static variable in class Foo
    • Module B exposes a function to access Foo's static variable
  • In Python, call A's setter followed by B's getter. You would expect the value to match, but (at least in our case) B's static will still be set to its default value.

This only breaks across the extension module boundary (separate .pyd files). The issue has to do with dynamic linkage behavior on Windows. Here is a wiki page that discusses a different manifestation of the same issue.

Our solution was to merge the C++ modules into a single Boost.Python module, and therefore a single shared object. To mimic our old setup, we added submodules to maintain the separation between libraries:

// Put all following definitions in the current scope into a namespace-esque module
#define MAKE_MODULE(modName) \
    boost::python::object module(boost::python::handle<>(boost::python::borrowed(PyImport_AddModule("pymod."#modName)))); \
    scope().attr(#modName) = module; \
    scope s(module);

using namespace boost::python;

BOOST_PYTHON_MODULE(pymod)
{
    object package = scope();
    package.attr("__path__") = "pymod";

    // pymod.a
    {
        MAKE_MODULE(a);
        exportModuleA();
    }

    // pymod.b
    {
        MAKE_MODULE(b);
        exportModuleB();
    }

    ...
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top