I am embedding python in my C++ application using boost python. I am a C++ programmer, with very limited knowledge of Python.
I have a C++ class, PyExpression
. Each instance of this class has a string expStr
, which is a short user-entered (at runtime) python program, that is executed by calling boost::python::exec
. Briefly, I have this set up as:
//import main and its globals
bp::object main = bp::import("__main__");
bp::object main_namespace = main.attr("__dict__");
where main
and main_namespace
are members of the C++ class PyExpression
.
void PyExpression::Run()
{
bp::object pyrun = exec(expStr,main_namespace);
}
The problem here is that different C++ instances of PyExpression
modify the same global python namespace, main_namespace
, and I want each PyExpression
instance to have its own "global" namespace.
If I pass in boost::python::dict class_dict
instead of main_namespace
above, it works at a basic level. But if PyExpression::expStr
imports a module, e.g. import sys
, then I get an ImportError
. Also, using class_dict
, I can no longer call globals()
, locals()
, vars()
, as they all become undefined.
I have also tried exposing PyExpression
as a python module. Briefly,
BOOST_PYTHON_MODULE(PyExpModule)
{
bp::class_<PyExpression>("PyExpression", bp::no_init)
//a couple .def functions
}
int pyImport = PyImport_AppendInittab( "PyExpModule", &initPyExpModule );
bp::object thisExpModule = bp::object( (bp::handle<>(PyImport_ImportModule("PyExpModule"))) );
bp::object PyExp_namespace = thisExpModule.attr("__dict__");
Unfortunately, using PyExp_namespace
, again I get the ImportError when the string to be executed imports a python module, and again, the namespace is shared between all instances of PyExpression
.
In short, I want to be able to use a namespace object/dictionary, that is preferably a class member of PyExpression
, have only that instance of PyExpression
have access to the namespace, and the namespace to act like a global namespace such that other modules can be imported, and the `globals(), locals(), vars() are all defined.
If anyone can point me to a sketch of working code, I would very much appreciate it. I can't find relevant material on this problem.
Before providing a solution, I want to provide some clarification on Python behavior.
Boost.Python's
object
is essentially a higher-level handle of a smart pointer. Thus, multipleobject
instances may point to the same Python object.The above code imports a module named
__main__
. In Python, modules are essentially singletons due to the import behavior. Therefore, although the C++main_module
may be a member of the C++PyExpression
class, they all point to the same Python__main__
module, as it is a singleton. This results inmain_namespace
pointing to the same namespace.Much of Python is built around dictionaries. For example, with an
example
module:There are 3 dictionaries of interests:
example.__dict__
is theexample
module's namespace.example.Foo.__dict__
is a dictionary that describes theFoo
class. Additionally, it would contain the equivalent of C++'s static member variables and functions.example.Foo().__dict__
is a dictionary containing instance-specific variables. This would contain the equivalent of C++'s non-static member variables.The Python
exec
statement takes two optional arguments:globals()
. If the second argument is omitted, then it is also used forlocals()
.locals()
. Variable changes occurring withinexec
are applied tolocals()
.To get the desired behavior,
example.Foo().__dict__
needs to be used aslocals()
. Unfortunately, this becomes slightly more complicated because of the following two factors:import
is a Python keyword, the CPython implementation is dependent on__builtins__.__import__
. Thus, there needs to be a guarantee that the__builtin__
module is assessable as__builtins__
within the namespace passed toexec
.Foo
is exposed as a Python class through Boost.Python, then there is no easy way to access the PythonFoo
instance from within the C++Foo
instance.To account for these behaviors, the C++ code will need to:
__dict__
.__builtin__
module into the Python object's__dict__
.__dict__
to the C++ object.Here is an example solution that only sets variables on the instance for which code is being evaluated:
And the output:
Due to how libraries are loaded from imports, it may require providing arguments to the linker that will cause all symbols, not only used ones, to the dynamic symbol table. For example, when compiling the above example with gcc, using
-rdynamic
was required. Otherwise,import time
will fail due to an undefinedPyExc_IOError
symbol.Python does not provide a 100% reliable isolation mechanism for this kind of task. That said, the essential tool you are looking for is the Python C-API
Py_NewInterpreter
, which is documented here. You will have to call it upon the creation of yourPyExpression
object, to create a new (semi)-isolated environment (N.B.: the destructor should callPy_EndInterpreter
).This is untested, but I'd guess something liket this would do the job:
You may wrap that into an object. If you wish to do so, you must manage the "thread" state as explained in this other stackoverflow thread.