pythonc++embeddingpycxx

Load module into embedded Python runtime using PyCXX


I'm using PyCXX to create a C++ wrapper around an embedded Python runtime.

PyCXX doesn't seem to have an example of executables, so I'm trying to adapt the existing example code.

I can get the Python interpreter up and running easily:

extern "C" int Py_Main(int argc, PY_CHAR** argv);

int main(int argc, const char * argv[])
{
    Py_Initialize();
    PyRun_SimpleString( "print('hello world') \n" );
    Py_Finalize();
}

That puts a fully functional Python prompt in my Xcode debug/output window.

Next, I expose a test C++ class so that it becomes visible within Python. There is a range class that has been written for this purpose:

extern "C" int Py_Main(int argc, PY_CHAR** argv);

int main(int argc, const char * argv[])
{
    Py_Initialize();
    range::init_type();

    //test_extension_object();  <-- test-suite for 'range'

    Py_Main(argc, (PY_CHAR**)argv); // wrong but works(!)

    /*
     This will launch a Python prompt in Xcode's output window
     You can type:
         >>> x = range(1,20,3)
         >>> i = [a for a in x]
         >>> i
         [1, 4, 7, 10, 13, 16, 19]
         >>> quit()
         Program ended with exit code: 0
     */
    Py_Finalize();

}

Ok, so that works also.

But now I'm trying to load a module.

There is a simple demo module called "simple.cxx", which contains:

extern "C" EXPORT_SYMBOL PyObject *PyInit_simple()
{
    static simple_module* simple = new simple_module;
    return simple->module().ptr();
}

And the simple_module class derives from PyCXX's ExtensionModule class, which derives from a ExtensionModuleBase class, which has the initialiser:

void ExtensionModuleBase::initialize( const char *module_doc )
{
    memset( &m_module_def, 0, sizeof( m_module_def ) );

    m_module_def.m_name = const_cast<char *>( m_module_name.c_str() );
    m_module_def.m_doc = const_cast<char *>( module_doc );
    m_module_def.m_methods = m_method_table.table();
    // where does module_ptr get passed in?

    m_module = PyModule_Create( &m_module_def );
}

If I understand correctly, the intended use is that one should compile this .cxx into a library (.so on OS X), and place it somewhere in Python's search-path.

But it should be possible to get this working without having to compile a separate library. This is what I'm trying to do.

extern "C"
{
    int Py_Main(int argc, PY_CHAR** argv);
    PyObject *PyInit_example();
}

int main(int argc, const char * argv[])
{
    PyImport_AppendInittab("spam", &PyInit_example);

    Py_Initialize();
    Py_Main(argc, (PY_CHAR**)argv); // wrong but works(!)
    Py_Finalize();
}

I'm using the documentation here: https://docs.python.org/3.4/extending/embedding.html which tells me to use PyImport_AppendInittab.

Now I should be able to see this module from the prompt. It is called simple.

>>> import simple
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named 'simple'

>>> import sys
>>> sys.modules.keys()
dict_keys(['_weakrefset', 'copyreg', 'posix', '_io', 'encodings.aliases', '__main__', '_frozen_importlib', '_sysconfigdata', 'sys', 'encodings.utf_8', '_osx_support', 'marshal', 'builtins', 'encodings.ascii', 'abc', 'stat', '_weakref', 'atexit', '_bootlocale', 'rlcompleter', '_collections_abc', 're', 'readline', '_thread', 'zipimport', 'sre_constants', '_sitebuiltins', 'encodings.latin_1', '_sre', 'codecs', '_codecs', 'sysconfig', '_locale', 'posixpath', '_stat', 'encodings', 'genericpath', 'os.path', 'site', 'sitecustomize', 'sre_parse', 'io', 'os', 'errno', '_warnings', 'signal', 'sre_compile', '_imp'])

It doesn't seem to have worked.

What am I missing?


Solution

  • I am a chimp. "spam" needed to be replaced with "simple", and it works.

    I will leave the question rather than deleting it, as it contains useful steppingstones for any future PyCXX explorer.