3

I've been working on integrating a scripting subsystem into an existing architecture and have been employing the use of the boost.python library.

My goal is to have the python environment that I create to be identical, or as close to, that which is expected in existing code - i.e. preserving namespace conventions, etc. The trick here is that I have two source files illustrating a use case that works if I fire up the interpreter standalone but do not work in my environment, meaning I must be doing something wrong.

As a disclaimer I'm not an experienced python user so I'm bound to butcher the correct terminology here.

core.py

import sys

def func():    
    print "sys is",sys

print "call from core"  
func()

main.py

import core

print "---------GLOBALS-------------"
globals_ = dict(globals())
for g in globals_:
    print g

print "--------------LOCALS---------"   
locals_ = dict(locals())
for l in locals_:
    print l

print "call from main"
core.func()

Invoking python main.py from command prompt

call from core
sys is <module 'sys' (built-in)>
---------GLOBALS-------------
core
__builtins__
__name__
__file__
__doc__
__package__
--------------LOCALS---------
core
g
__builtins__
__file__
globals_
__package__
__name__
__doc__
call from main
sys is <module 'sys' (built-in)>

I understand the steps as being:

  1. main invokes import to import core.py.
  2. The core module is loaded; the core namespace and the attributes it provides become accessible from main. In the process, core invokes its func method, printing what is bound to sys.
  3. main invokes the function bound under the module core.

Example of how I bring up the scripting environment:

void Run()
{
    Py_Initialize();
    if(PyErr_Occurred())
    {
        PyErr_Print();
    }

    // Install builtins in __main__
    boost::python::object main_module = boost::python::import(ToplevelScope.c_str());
    boost::python::object main_namespace = main_module.attr("__dict__");
    boost::python::dict main_dictionary = boost::python::extract<boost::python::dict>(main_namespace);
    main_dictionary["__builtins__"] =  boost::python::handle<>(boost::python::borrowed(PyEval_GetBuiltins()));

    // Load modules
    LoadModule("core.py",GetDataForCore());
    LoadModule("main.py",GetDataForMain());
}

void LoadModule(std::string name, char* data)
{
    // First, see if the module already exists.
    PyObject* new_module;
    if( new_module = PyImport_AddModule(name.c_str()) )
    {
        if( PyDict_GetItemString(new_module,"__name__") )
        {
            return; // already loaded. no work to do.
        }
    }

    // Initialize default namespace parameters - global, local
    boost::python::dict base_dict = boost::python::dict();
    base_dict["__builtins__"] = boost::python::handle<>(PyImport_AddModule("__builtin__"));
    base_dict["__name__"] = name.c_str();
    base_dict["__file__"] = name.c_str();
    base_dict["__doc__"] = "None";
    base_dict["__package__"] = name.c_str();

    boost::python::exec(    data,
                            boost::python::dict(base_dict),  // make a copy of base_dict for both global & local
                            boost::python::dict(base_dict) );
}

Resultant output:

call from core
sys is <module 'sys' (built-in)>
---------GLOBALS-------------
core
__builtins__
__name__
__file__
__doc__
__package__
--------------LOCALS---------
core
g
__builtins__
__file__
globals_
__package__
__name__
__doc__
call from main
[LOG] Framework error while loading script main.py.
Traceback (most recent call last):
  File "<string>", line 17, in <module>
AttributeError: 'module' object has no attribute 'func'

It appears that the function in core somehow isn't accessible to main even after an import.

I suspect this has to do with me not fully initializing the core module. Replacing exec with PyRun_String, PyImport_ImportModule... and they all yield the same result. Here I'm creating a copy of a base dictionary with name set to the module name (core in this case) and supplying it as the global & local dictionaries.

I think that I'm not fully initializing the module somehow and will be doing a lot more reading to put it together... Any insight will be greatly appreciated!!

3
  • 1
    Why have you created your own load module function, rather than using PyImport_AddModule as you do for __builtins__? Also, the LoadModule function doesn't actually seem to be loading the module, rather just executing the module code and then disposing of the module. Commented Oct 30, 2014 at 11:16
  • @Dunes Thanks! I have a LoadModule function because the process for getting at the text buffer is quite involved. I have my scripts in RAM bundled in my own class and query said class to get the buffer among other things. Sorry; I omitted a step where I do call PyImport_AddModule. Updating post. The doc implies this won't do the load though (This function does not load or import the module; if the module wasn’t already loaded, you will get an empty module object). I don't know how to "load" the module instance apart from to exec its code. Commented Oct 30, 2014 at 18:12
  • After further review I think I have to Import on the newly created module object to populate its structures. As documented here, it looks like PyImport_ExecCodeModule after compiling my string is the way to go. Commented Oct 30, 2014 at 20:12

1 Answer 1

1

It seems likes you're trying to reinvent the wheel. boost::python::import does exactly what you want to do. Loads a module, or retrieves it from sys.modules if it's already been imported before. If you don't want to use boost then you can use PyObject* PyImport_ImportModule(const char *name). Just make sure the module is on python's path.

However, if the module doesn't exist as a file (say the source only exists in memory), then the following will recreate that functionality (it doesn't do any error checking, you'll need to add that yourself).

py::object LoadModule(std::string name, const char* module_source)
{
    namespace py = boost::python;

    // create module name and filenames
    py::object name_str(py::str(name.c_str()));
    std::string file = name;
    file += ".py";
    py::object file_str(py::str(file.c_str()));

    // get helper modules
    py::object types(py::import("types"));
    py::object sys(py::import("sys"));

    if (py::dict(sys.attr("modules")).has_key(name_str)) {
        return sys.attr("modules")[name_str];
    }

    // create empty module
    py::object module(types.attr("ModuleType")(name_str));

    // init module dict
    py::dict base_dict = py::extract<py::dict>(module.attr("__dict__"));
    base_dict["__builtins__"] = py::import("__builtins__");
    base_dict["__name__"] = name_str;
    base_dict["__file__"] = file_str;

    // execute module code in module context
    py::exec(module_source, base_dict, base_dict);

    // add module to sys.modules
    sys.attr("modules")[name] = module;

    return module;
}
Sign up to request clarification or add additional context in comments.

1 Comment

This is great, thank you! This is a lot cleaner than what I've managed to get working which uses base Py_ calls.

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.