1

I am embedding python in my C++ application, using boost python.

I would like to be able to call a boost python function object, and associate a global name space with that function call. Specifically, the simplified relevant code is:

bp::object main = bp::import("__main__");
bp::object main_namespace = main.attr("__dict__");


//Put the function name runPyProg in the main_namespace

bp::object PyProg = exec(
        "import cStringIO\n"
        "import sys\n"
        "sys.stderr = cStringIO.StringIO()\n"
        "def runPyProg(exp):\n"
        "    print exp\n"
        "    exec(exp)\n"
        "    return\n"
        "\n",main_namespace);

//Now call the python function runPyProg with an argument

bp::object py_fn = main.attr("runPyProg");
py_fn(expStr)

I know that when I use the boost python exec() function, I can send in the global namespace, as shown above. My question is how do I associate main_namespace with the python function when I call py_fn? My final goal is that local variables from runPyProg will be placed in the main_namespace.

Thank you.

user773494
  • 37
  • 2
  • 6

1 Answers1

4

If I understand the question correctly, then it should be as simple as specifying the context in which exec will execute. A function or method can access the namespace in which it is defined via globals(). Thus, calling globals() from within runPyProg() will return the Python equivalent of main_namespace. Additionally, exec takes two optional arguments:

  • The first argument specifies the dictionary that will be used for globals(). If the second argument is omitted, then it is also used for locals().
  • The second argument specifies the dictionary that will be used for locals(). Variable changes occurring within exec are applied to locals().

Therefore, change:

exec exp

to

exec exp in globals()

and it should provide the desired behavior, where exp can interact with global variables in main_namespace.


Here is a basic example:

#include <boost/python.hpp>

int main()
{
  Py_Initialize();

  namespace python = boost::python;
  python::object main = python::import("__main__");
  python::object main_namespace = main.attr("__dict__");

  //Put the function name runPyProg in the main_namespace
  python::exec(
    "def runPyProg(exp):\n"
    "    print exp\n"
    "    exec exp in globals()\n"
    "    return\n"
    "\n", main_namespace);

  // Now call the python function runPyProg with an argument
  python::object runPyProg = main.attr("runPyProg");

  // Set x in python and access from C++.
  runPyProg("x = 42");
  std::cout << python::extract<int>(main.attr("x")) << std::endl;

  // Set y from C++ and access within python.
  main.attr("y") = 100;
  runPyProg("print y");

  // Access and modify x in python, then access from C++.
  runPyProg("x += y");
  std::cout << python::extract<int>(main.attr("x")) << std::endl;
}

Commented output:

x = 42          // set from python
42              // print from C++
                // y set to 100 from C++
print y         // print y from python
100             //
x += y          // access and modify from python
142             // print x from C++
Tanner Sansbury
  • 51,153
  • 9
  • 112
  • 169
  • So I've realized that if I have a C++ class PyExpression, and each instance of the class calls boost python exec() to execute its own expression, then all instances are actually using the same global namespace. Is there a way to keep the scope of the namespace to each C++ instance of PyExpression? When I try passing in a boost::python::object that is not initialized to main.attr("__dict__"), the exec() throws an error. – user773494 May 14 '13 at 14:45
  • @user773494: Namespaces are just dictionaries in Python. Either update `runPyProg` to accept the namespace in which it will execute the expression (likely the `__dict__` object on the `PyExpression` instance), or use the [`inspect`](http://docs.python.org/2/library/inspect.html) module to extract the desired namespace from the stack. – Tanner Sansbury May 14 '13 at 15:05
  • Would you be able to point me to a working example of sending in the dict of PyExpression? Having trouble finding anything online. I have essentially exposed the PyExpression class using `BOOST_PYTHON_MODULE(PyExpression) { }` I am importing the module and obtaining the namespace as such `bp::object thisExpModule = bp::object( (bp::handle<>(PyImport_ImportModule("PyExpression"))) );` and `bp::object instance_namespace = thisExpModule.attr("__dict__");` When I send in instance_namespace, it works ok as a dictionary namespace, but again all instances of PyExpression modify the same object. Tnx! – user773494 May 14 '13 at 21:50
  • @user773494: Python modules are essentially singletons due to the [import behavior](http://docs.python.org/2/reference/simple_stmts.html#the-import-statement). If you had a `Foo` class in the `PyExpression` module, then `PyExpression.__dict__` is the module namespace. On the other hand, with `a, b = PyExpression.Foo(), PyExpression.Foo()`, `a.__dict__` and `b.__dict__` are namespaces specific to each instance. If you need more help to get a working example, then consider creating a new question with the relevant pieces of code. – Tanner Sansbury May 15 '13 at 13:29
  • Thanks Tanner, I started a new thread here [link](http://stackoverflow.com/questions/16569106/boost-python-using-a-namespace-other-than-main-global) – user773494 May 15 '13 at 17:15