3

I'm using embedded python interpreter in my app (iOS to be detailed).

Sometimes sys.exit(1) is invoked in interpreted script and it finishes the whole app process instead of stopping PyObject_callObject() invocation. I've tried to check errors using PyErr_Occured but it did not help.

How to prevent sys.exit(N) to finish the whole process in embedded python?

NSString *outputFile = nil;
for (int i=0; i<args.count; i++) {
    if ([@"-o" isEqualToString:args[i]]) {
        outputFile = args[i + 1];
        break;
    }
}

PyEval_AcquireLock();
PyThreadState *subState = Py_NewInterpreter();

PyObject *pModuleName, *pModule, *pFunc;

// init python
NSString *pythonHome = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:@"/"];

Py_SetProgramName("python");
char *cPythonHome = (char*)[pythonHome UTF8String];

setenv("PYTHONPATH", cPythonHome, 1); // VERY IMPORTANT !!!
Py_SetPythonHome(cPythonHome);

NSString *libsPath = [pythonHome stringByAppendingString:@"lib/python2.7"];

if (!Py_IsInitialized())
    Py_Initialize();

// invoke
int result = 0;

NSString *scriptFilename = args[1];
NSString *moduleName = [[scriptFilename lastPathComponent] stringByDeletingPathExtension];

pModuleName = PyString_FromString([moduleName UTF8String]); // module (script) name
pModule = PyImport_Import(pModuleName);

if (PyErr_Occurred())
    PyErr_Print();

if (pModule != NULL) {
    pFunc = PyObject_GetAttrString(pModule, "main__"); // module must have "def main__(args)"

    if (pFunc != NULL && PyCallable_Check(pFunc)) {

        // prepare args
        PyObject *pArgs = PyList_New(args.count-1);
        for (int i=0; i<args.count-1; i++) {
            NSString *arg_i = args[i + 1]; // skip first argument (it's program name)
            PyObject *pEachArg = PyString_FromString([arg_i UTF8String]);
            PyList_SetItem(pArgs, i, pEachArg);
            // WARNING: don't Py_DECREF for each argument
        }

        // for some reason arguments should be passed as s Tuple
        PyObject *pTuple = PyTuple_New(1);
        PyTuple_SetItem(pTuple, 0, pArgs);

        // call func
        NSLog(@"Invoke %@ via main__(args)", scriptFilename);
        PyObject *pyResult = PyObject_CallObject(pFunc, pTuple); // process killed here !

        if (pyResult == NULL || PyErr_Occurred()) {
            // print error
            PyErr_Print();

            // fix error
            PyErr_Clear();
            if (PyErr_ExceptionMatches(PyExc_SystemExit)) {
                result = 3; // error: system.exit() called
            } else
                result = 4; // error: unknown exception
        }

        if (pyResult != NULL)
            Py_DECREF(pyResult);

        Py_DECREF(pTuple);
        Py_DECREF(pArgs);
    } else
        result = 2; // error: can't find "def main__()" in module

    if (pFunc != NULL)
        Py_XDECREF(pFunc);
} else
    result = 1; // error: can't import module

if (pModule != NULL)
    Py_DECREF(pModule);

Py_DECREF(pModuleName);

// restore parent interpreter
Py_EndInterpreter(subState);

PyEval_ReleaseLock();
1
  • Anybody? Any solution? Commented Dec 9, 2014 at 16:01

1 Answer 1

1

I had to hack Python sources and create my own function:

int MyPyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit, PyCompilerFlags *flags)
{
    PyObject *m, *d, *v;
    const char *ext;
    int set_file_name = 0, ret, len;

    m = PyImport_AddModule("__main__");
    if (m == NULL)
        return -1;
    d = PyModule_GetDict(m);
    if (PyDict_GetItemString(d, "__file__") == NULL) {
        PyObject *f = PyString_FromString(filename);
        if (f == NULL)
            return -1;
        if (PyDict_SetItemString(d, "__file__", f) < 0) {
            Py_DECREF(f);
            return -1;
        }
        set_file_name = 1;
        Py_DECREF(f);
    }
    len = strlen(filename);
    ext = filename + len - (len > 4 ? 4 : 0);

    /*
    if (maybe_pyc_file(fp, filename, ext, closeit)) {
        // Try to run a pyc file. First, re-open in binary
        if (closeit)
            fclose(fp);
        if ((fp = fopen(filename, "rb")) == NULL) {
            fprintf(stderr, "python: Can't reopen .pyc file\n");
            ret = -1;
            goto done;
        }
        // Turn on optimization if a .pyo file is given
        if (strcmp(ext, ".pyo") == 0)
            Py_OptimizeFlag = 1;
        v = run_pyc_file(fp, filename, d, d, flags);
    } else { */
        v = PyRun_FileExFlags(fp, filename, Py_file_input, d, d,
                              closeit, flags);
    //}

    if (v == NULL) {
        //PyErr_Print(); // crashed here!
        ret = -1;
        goto done;
    }
    Py_DECREF(v);
    if (Py_FlushLine())
        PyErr_Clear();
    ret = 0;
done:
    if (set_file_name && PyDict_DelItemString(d, "__file__"))
        PyErr_Clear();
    return ret;
}

I had to use _SimpleFile instead of _SimpleString but i believe you can change _SimpleString in similar way.

The reason was PyErr_Print which caused app crash. The one drawback is that i had to comment pyc file checking and usage as maybe_pyc_file is not exported and is unavailable.

One more note: if SystemExit was raised (by sys.exit(1) f.e.) don't use PyErr_Print. Use the next checking:

if (PyErr_Occurred()) {
        pythonSuccess = NO;

        if (PyErr_ExceptionMatches(PyExc_SystemExit)) {
            NSLog(@"sys.exit() in python");
        } else {
            // print error
            PyErr_Print();
        }
        // fix error
        PyErr_Clear();
    }

If you have better solution, let us know.

Sign up to request clarification or add additional context in comments.

Comments

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.