4

I have some code that will go to a directory (Folder 1 for demonstration purposes), then call a function called function in the file python_function.py. The code looks like this:

#include <Python.h>
#include <string>
#include <iostream>

int main()
{
    PyObject *pName, *pModule, *pDict, *pFunc;

    setenv("PYTHONDONTWRITEBYTECODE", " ", 1);

    // Initialize the Python Interpreter
    Py_Initialize();

    //CALL FUNCTION FROM FOLDER 1:
    std::wstring pathWide = L"./Folder 1";
    PySys_SetPath(pathWide.c_str());

    // Build the name object
    pName = PyUnicode_FromString((char*)"python_function");
    // Load the module object
    pModule = PyImport_Import(pName);
    // pDict is a borrowed reference
    pDict = PyModule_GetDict(pModule);
    // pFunc is also a borrowed reference
    pFunc = PyDict_GetItemString(pDict, (char*)"function");

    if (pFunc != NULL)
    {
        if (PyCallable_Check(pFunc))
        {
            PyObject *pResult;

            pResult = PyObject_CallFunction(pFunc, "");

            Py_DECREF(pResult);
        }
        else {PyErr_Print();}
    }
    else {std::cout << "pFunc is NULL!" << std::endl;}

    // Clean up
    Py_DECREF(pFunc);
    Py_DECREF(pDict);
    Py_DECREF(pModule);
    Py_DECREF(pName);

    // Finish the Python Interpreter
    Py_Finalize();

    return 0;
}

This code compiles and works perfectly on my system, but as soon as I want to call another function in a second directory, called Folder 2, I get the error: Segmentation Fault (core dumped). This is the code:

#include <Python.h>
#include <string>
#include <iostream>

int main()
{
    PyObject *pName, *pModule, *pDict, *pFunc;

    setenv("PYTHONDONTWRITEBYTECODE", " ", 1);

    // Initialize the Python Interpreter
    Py_Initialize();

    //CALL FUNCTION FROM FOLDER 1:
    std::wstring pathWide = L"./Folder 1";
    PySys_SetPath(pathWide.c_str());

    // Build the name object
    pName = PyUnicode_FromString((char*)"python_function");
    // Load the module object
    pModule = PyImport_Import(pName);
    // pDict is a borrowed reference
    pDict = PyModule_GetDict(pModule);
    // pFunc is also a borrowed reference
    pFunc = PyDict_GetItemString(pDict, (char*)"function");

    if (pFunc != NULL)
    {
        if (PyCallable_Check(pFunc))
        {
            PyObject *pResult;

            pResult = PyObject_CallFunction(pFunc, "");

            Py_DECREF(pResult);
        }
        else {PyErr_Print();}
    }
    else {std::cout << "pFunc is NULL!" << std::endl;}

    //CALL FUNCTION FROM FOLDER 2:
    pathWide = L"./Folder 2";
    PySys_SetPath(pathWide.c_str());

    // Build the name object
    pName = PyUnicode_FromString((char*)"python_function");
    // Load the module object
    pModule = PyImport_Import(pName);
    // pDict is a borrowed reference
    pDict = PyModule_GetDict(pModule);
    // pFunc is also a borrowed reference
    pFunc = PyDict_GetItemString(pDict, (char*)"function");

    if (pFunc != NULL)
    {
        if (PyCallable_Check(pFunc))
        {
            PyObject *pResult;

            pResult = PyObject_CallFunction(pFunc, "");

            Py_DECREF(pResult);
        }
        else {PyErr_Print();}
    }
    else {std::cout << "pFunc is NULL!" << std::endl;}

    // Clean up
    Py_DECREF(pFunc);
    Py_DECREF(pDict);
    Py_DECREF(pModule);
    Py_DECREF(pName);

    // Finish the Python Interpreter
    Py_Finalize();

    return 0;
}

The error occurs after I call the first function, so it seems like its not changing directories or something. I'm using Ubuntu and I have python 3.4

I have tried other methods of changing directories, not just PySys_SetPath, but also setenv("PYTHONPATH", path, 1);

NOTE: I'm not worried about error detection right now, I'd rather have code that works in ideal circumstances, then worry about imperfect circumstances.

EDIT:

Debug output:

#0 0x7ffff79b16cb   PyModule_GetDict() (/usr/lib/x86_64-linux-gnu/libpython3.4m.so.1.0:??)
#1 0x4010e6 main() (/home/ben/Documents/Programming/Projects/PYTHON TEST/main.cpp:23)

Oddly the debug says that the error happens at line 23, but line 23 doesn't cause an error if you run the first code segment

IN RESPONSE TO PETER BRITTAIN'S ANSWER:

If I replace the second PyImport_Import() with PyImport_ReloadModule(), I get an error printed to the console, like this:

ImportError: No module named 'imp'
Error in sys.excepthook:
Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/apport_python_hook.py", line 53, in apport_excepthook
    if not enabled():
  File "/usr/lib/python3/dist-packages/apport_python_hook.py", line 24, in enabled
    import re
ImportError: No module named 're'

Original exception was:
ImportError: No module named 'imp'
Ben Hollier
  • 585
  • 2
  • 11
  • 32
  • 1
    I'm quite sure Python module names must not have spaces in them, so I'm really surprised that the above works at all. Have you tried this with valid module names? EDIT: Indeed, [Python module names](https://docs.python.org/3.4/reference/simple_stmts.html#grammar-token-module) must be valid identifiers, which can't contain whitespace characters. – taleinat Jul 09 '15 at 10:36
  • 1
    @taleinat Oops, I thought the code works because I took it from code I know works, I'll add some underscores now – Ben Hollier Jul 09 '15 at 10:47
  • 1
    @Orfby judging from your questions over the past few days, I can't help but wonder, have you looked at boost.python or cython? They will make your life much easier. – Nasser Al-Shawwa Jul 09 '15 at 21:03
  • 1
    @Nasser I've heard of boost.python but I feel like the big boost libraries is a bit overkill for a small part of my program (and I've had no luck compiling it). I haven't done very much research on cython though, but I'd imagine it hasn't got the same documentation as the official libraries. I'll have to do more experimentation though – Ben Hollier Jul 09 '15 at 21:12
  • It's not really good to write the `.c` file extended with Python yourself. Please let Cython do it for you – Himanshu Mishra Jul 15 '15 at 07:13

1 Answers1

4

EDIT: Updated with further answers to the bugs found.

You're failing to import your module in your debug output. When run outside of the debugger, you then hit the issue that you can't just import with the same import call. The full chain of problems is something like this.

When debugging:

  1. Your debugger is not setting the right current working directory.
  2. The relative path is not valid but is accepted by PySys_SetPath().
  3. You therefore get NULL from PyImport_Import() indicating that the import fails (as documented here under the debugger.
  4. Since you aren't checking for errors, you pass NULL into the next function, which tries to dereference the pointer and fails with a segmentation fault.

I hit an issue with Python2.7 (using char* rather than wchar* - as covered in the comments below). Putting that to one side, when running normally:

  1. The relative path is valid and accepted by PySys_SetPath().
  2. You therefore manage to load the module the first time.
  3. You then run through the rest of the code, but this time it segmentation faults at the second import. This is because you can't reload the module this way. You need to use PyImport_ReloadModule() instead.
  4. Even with that fix, you find you can't load standard libraries. That's because you've removed the rest of your system path. You need to follow the advice here.

So, the fixes are:

  1. Use std::string instead of std::wstring (for Python 2.x).
  2. Use PyImport_ReloadModule() when reloading modules.
  3. Make sure that you include the full sys.path when setting your path.
  4. Check your errors - I got explicit module import errors using PyErr_Print() for most of the problems.
Community
  • 1
  • 1
Peter Brittain
  • 13,489
  • 3
  • 41
  • 57
  • 1
    You said I should use absolute paths, but could I use '../' to go back a directory? (Mostly because in my circumstances absolute paths aren't possible without platform independent functions) – Ben Hollier Jul 13 '15 at 07:08
  • 1
    And also, I tried using absolute paths instead of relative paths and I get the same thing, no error when running the first code, error running the second – Ben Hollier Jul 13 '15 at 08:14
  • 1
    @Orfby - As I said in the answer, the real issue is a lack of any error handling. You are almost certainly failing to load the module and so should check the result for `PyImport_Import()` and (if it has failed as I suspect), dump the error from Python using something from [here](https://docs.python.org/3.4/c-api/exceptions.html). – Peter Brittain Jul 13 '15 at 08:28
  • 1
    I've tried adding `PyErr_Print()` after every line of code, and checked every `Py_Object` to see if it is `NULL` and I get no errors (I can update the answer with the error checking if you like). But, other than those two techniques I don't know how to detect other errors – Ben Hollier Jul 13 '15 at 10:26
  • 1
    @Orfby OK - I've compiled it for myself and found various bugs. Will update the answer now. – Peter Brittain Jul 13 '15 at 19:53
  • 1
    According to [here](https://docs.python.org/3.4/c-api/sys.html#c.PySys_SetPath) I have to use `wchar`s for `PySys_SetPath()` (maybe you're thinking of python 2.7?) – Ben Hollier Jul 13 '15 at 20:15
  • 1
    But, by using `PyImport_Import` I get import errors, rather than a segfault (which I'll put in the question) – Ben Hollier Jul 13 '15 at 20:29
  • 1
    @Orfby Yup - I was using 2.7. Using wchar for 3.x looks right. Your final problem looks to be [this](http://stackoverflow.com/questions/7624529/python-c-api-doesnt-load-module). – Peter Brittain Jul 13 '15 at 21:04