Sitemap

McMillan Enterprises, Inc.
Python Pages
  Sockets HOWTO
  Distributing Python Programs
  A Python C++ API
  Embedding Python
    Scripting Your App with Python
  Stackless Python
  MkSQL
  Import Hooks
Java Samples
Sponsoring ME Inc.
About ME Inc.

Scripting Your App With Python

OK, so you've got years of effort invested in your app, and you're not about to admit that rewriting it in Python would let you fix some pesky design flaws and make user-extensibility trivial (as well as getting you 90% of the way to cross-platform). All you want is to let your users script your application.

You're going to do two things. You're going to embed Python, and you're going to extend Python. The extension will expose your app's object model (or some piece thereof) to Python. That probably means that the extension module will provide access to a few root (global) objects, and these objects will have methods to navigate to other objects, and that all such user-accessible objects get Python wrappers. I'm not going to describe how to do a non-trivial extension; there are lots of examples out there (including the entire Modules and Objects source directories).

What I will show you is how simple it is to both embed and extend at the same time. In the following code, pretend that the "inc" method is non-trivial. It could, for example, be called "getRoot" and return a Python wrapper for your application's currently active Document object.

 
      #include "Python.h"
      void init_pyextension();
      int main(int argc, char* argv[])
      {
          long answer;
          PyObject *modname, *mod, *mdict, *func, *rslt;
          Py_Initialize();
          init_pyextension();
          modname = PyString_FromString("testqqq");
          mod = PyImport_Import(modname);
          if (mod) {
              mdict = PyModule_GetDict(mod);
              func = PyDict_GetItemString(mdict, "doit"); /* borrowed reference */
              if (func) {
                  if (PyCallable_Check(func)) {
                      rslt = PyObject_CallFunction(func, "(s)", "this is a test");
                      if (rslt) {
                          answer = PyInt_AsLong(rslt);
                          Py_XDECREF(rslt);
                      }
                  }
              }
              Py_XDECREF(mod);
          }
          Py_XDECREF(modname);
          Py_Finalize();
          return 0;
      }
      /* now a method we need to expose to Python */
      long inc(long i) {
          return ++i;
      }
      /* and the magic that exposes it - a builtin module */
      /* first, the wrapper function */
      static PyObject *py_inc(PyObject *self, PyObject *args)
      {
          long i;
          if (!PyArg_ParseTuple(args, "l", &i))
              return NULL;
          return Py_BuildValue("l", inc(i));
      }
      /* now the module's function table */
      static PyMethodDef genius_methods[] = {
          {"inc",     py_inc,     1,  "a silly example method"},
          {NULL,      NULL}       /* sentinel */
      };
      /* Python will call this when the module is imported */
      void init_pyextension()
      {
          PyImport_AddModule("genius");
          Py_InitModule("genius", genius_methods);
      }
      

A Note On Threads

When embedding, threads are either no problem at all, or an absolute nightmare.

If you are single-threaded, they are obviously no problem. If only one thread is running Python, it is no problem. Callbacks are not normally a problem, either, since they almost always occur on the same thread that triggered them.

If you will be using multiple threads with Python, you will have to be very careful. You will have to make sure that Python is in threading mode (normally by calling PyEval_InitThreads() in the startup code). From this point forward, every thread calling into Python will have to have a PyThreadState associated with it. If you're letting Python create the threads, this is no problem (you'll just have to make sure that your extension module is threadsafe). But if you're creating threads that can use Python, you have some work to do.

One approach is get ahold of the PyInterpreterState that is created when you initialize Python, (yes, Python allows you to create multiple interpreters, and no, I'm not going to discuss it). When you need to call into Python on a new thread, use PyThreadState_New(PyInterpreterState *interp) to create a new PyThreadState, and make sure you keep track of the association between the OS thread and the PyThreadState. If you ever call into Python on a thread that Python does not know about - BOOM, end of the road.

If you don't mind using C++, see SCXX, which can take care of this for you.

copyright 1999-2002
McMillan Enterprises, Inc.