Search code examples
c++pythonimportpython-c-apipython-embedding

Do PyImport_ImportModule and import statement load into different namespace?


Here is canonical example of a program extending embedded Python 3.x in C/C++:

#include <Python.h>
//// Definition of 'emb' Python module ////////////////////
static PyObject* emb_foo(PyObject *self, PyObject *args)
{
    char const* n = "I am foo";
    return Py_BuildValue("s", n);
}
static PyMethodDef EmbMethods[] = {
    {"foo", emb_foo, METH_VARARGS, "Returns foo"},
    {NULL, NULL, 0, NULL}
};
static PyModuleDef EmbModule = {
    PyModuleDef_HEAD_INIT, "emb", NULL, -1, EmbMethods,
    NULL, NULL, NULL, NULL
};
static PyObject* PyInit_emb(void)
{
    return PyModule_Create(&EmbModule);
}
//// Embedded Python with 'emb' loaded ////////////////////
int main()
{
    PyImport_AppendInittab("emb", &PyInit_emb);
    Py_Initialize();

    PyRun_SimpleString("import emb\n");       // (1)
    //PyImport_ImportModule("emb");           // (2)

    PyRun_SimpleString("print(emb.foo())\n"); // (3)

    Py_Finalize();
    return 0;
}

I add the emb module to built-ins of the embedded interpreter. I'd also like to import it automatically, so users don't have to issue import emb statement in their scripts supplied to my embedded interpreter. I'm trying two ways of importing, in lines (1) and (2).

The (1) works and the emb module can be found without explicit import in the simple test in line (3). However, if I comment out the line (1) and uncomment the line (2) to import with C API of Python 3 call, then the line (3) produces error:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
NameError: name 'emb' is not defined

I'd like to understand what is the difference here between the two manners of importing. Do they import module into different namespaces / scopes ?

The Python 3 documentation led me along this path:

  1. PyImport_ImportModule is best described by referring to the built-in Python function __import__()
  2. __import__() function is invoked by the import statement.

Perhaps I made a mistake assuming PyImport_ImportModule is one-to-one equivalent and I should be using PyImport_ImportModuleEx with correct (which exactly?) globals and locals, so my 'emb' lands in global namespace of my embedded interpreter.


Solution

  • __import__ doesn't put the module in any namespace at all, but returns it instead. import calls __import__, plus it stores the result in a variable. The docs say that import spam does something similar to:

    spam = __import__('spam', globals(), locals(), [], 0)
    

    To get the same effect in the C API, you need to assign to the emb global. In other words, set the emb attribute on the __main__ module.

    PyObject* emb_module = PyImport_ImportModule("emb");
    PyObject* main_module = PyImport_AddModule("__main__");
    PyObject_SetAttrString(main_module, "emb", emb_module);
    Py_XDECREF(emb_module);
    /* (main_module is a borrowed reference) */