Embedding Python in Android

Adding log to the Python Interpreter

April 24, 2014




This article is part of a tutorial series.

  1. Embedding Python in Android
  2. Java Native Interface
  3. Embedding the Python Interpreter
  4. Adding log to the Python Interpreter
  5. Include Python Standard Library
  6. Compile C++ Cython Extensions
  7. Compile C Cython Extensions

If you have followed the previous article on this tutorial series, you now have a Python interpreter embedded in your Android application. Before we move on to fix the Python standard library, in this article we are going to set up some code to allow "prints" inside Python to be redirected to the Android logcat. The source code is adapted from the bootstrap code for python-for-android.

Redirecting Python stdout to Android logcat

In your IDE, open "jni/pyjni.c". Add the following code to your file.

static PyObject *androidembed_log(PyObject *self, PyObject *args) {
    char *logstr = NULL;
    if (!PyArg_ParseTuple(args, "s", &logstr)) {
        return NULL;
    }
    LOG(logstr);
    Py_RETURN_NONE;
}

static PyMethodDef AndroidEmbedMethods[] = {
    {"log", androidembed_log, METH_VARARGS,
     "Log on android platform"},
    {NULL, NULL, 0, NULL}
};

PyMODINIT_FUNC init_androidembed(void) {
    (void) Py_InitModule("androidembed", AndroidEmbedMethods);
    /* inject our bootstrap code to redirect python stdin/stdout */
    PyRun_SimpleString(
            "import sys\n" \
            "import androidembed\n" \
            "class LogFile(object):\n" \
            "    def __init__(self):\n" \
            "        self.buffer = ''\n" \
            "    def write(self, s):\n" \
            "        s = self.buffer + s\n" \
            "        lines = s.split(\"\\n\")\n" \
            "        for l in lines[:-1]:\n" \
            "            androidembed.log(l)\n" \
            "        self.buffer = lines[-1]\n" \
            "    def flush(self):\n" \
            "        return\n" \
            "sys.stdout = sys.stderr = LogFile()\n"
    );
}

In lines 1-8, we are creating a C extension which receives a Python argument, extracts a string from it and calls the pyjni.c "LOG" definition (which essentially logs to the Android console). Lines 10-14 defines a function called "log" and redirects it to the function at line 1. Line 17 informs the Python interpreter of a new module named "androidembed" which includes the methods defined in the function at line 10. The rest of the code execute a python script to redirect the sys.stdout and sys.stderror to the Android log.

For further information regarding extending the Python interpreter as this, I suggest to read these python docs.

Testing logcat from inside Python interpreter

To see it working, update the "pyjni" start() function to something like:

LOG("Initializing the Python interpreter");
Py_Initialize();
init_androidembed();
PyRun_SimpleString("print 'Hello from inside the Python interpreter!'");
PyRun_SimpleString("import this");
return 0;

Re-build the libraries with ndk-build, re-run the Android application and you will something like the following in the Android logcat.

I/pyjni   ( 9460): Initializing the Python interpreter
I/pyjni   ( 9460): Hello from inside the Python interpreter!
I/pyjni   ( 9460): Traceback (most recent call last):
I/pyjni   ( 9460):   File "<string>", line 1, in <module>
I/pyjni   ( 9460): ImportError: No module named this

As you can see, it correctly logs the python print instruction, and import errors are also correctly redirected to the log.

However, module "this" is a valid python module, so we still have to embedded anything of the standard library. That is the work for the next article.