Wiki

Embedding Python into Qt Applications

by Florian Link

Embedding scripting languages into C++ applications has become very common. Alongside many mainstream products, such as Microsoft Office and Macromedia Director, there is a growing trend with smaller, more specialized applications to offer integrated scripting to their users.

For several years, there have been only two mainstream solutions for embedding scripting languages into commercial Qt applications: QSA (JavaScript 2.0) from Trolltech and PyQt (Python) from Riverbank Computing. The Scripting Qt article in Qt Quarterly issue 10 gives a good overview of QSA, PyQt and some other solutions in action.

There have been some developments since that article was written, and, as of today, there are two new script bindings to look at:

  • QtScript, an ECMAScript interpreter with Qt bindings, is shipped as part of Qt 4.3.
  • PythonQt, used by MeVisLab, is a dynamic script binding to the Python interpreter.

While both QtScript and PythonQt make it very easy to embed scripting into your existing Qt Application, this article will focus on the PythonQt binding, leaving an in-depth look at QtScript for a later article to cover.

The Benefits of Scripting

Making a C++ application scriptable has several benefits:

  • A well designed application interface can provide an easy access point for both novice and power users.
  • Applications can be easily extended without requiring users to have deep C++ knowledge.
  • Scripting makes it easy to create macros and do batch processing.
  • Automated testing can be realized with scripting.
  • Scripting is cross-platform, so the scripts will work on all platforms the application runs on.
  • Scripting can speed up the prototyping stage a lot; e.g., your support team can add a feature/workaround by scripting much faster than it would take to develop it in C++ and redeploy the application.

Scripting APIs can range from a simple interface that allows activities such as batch processing of common application tasks to a fully-fledged interface that allows the user to customize/extend the menus and dialogs, and even to access the core functionality of the application (e.g., JavaScript in Web Browsers).

When considering scripting solutions for Qt applications, the following features are considered to be beneficial:

  • Easy integration into an existing Qt application.
  • Based on a well-known scripting language, so that new users do not need to learn a new language.
  • Good integration with the Qt framework; e.g., it should know about signals, slots and properties.
  • Support for marshalling data types between the scripting language and Qt, ideally supporting all QVariant types.
  • Support for debugging---when scripts get bigger, debugging becomes a crucial feature.

About PythonQt

Unlike PyQt and Qt Jambi, PythonQt is not designed to provide support for developers writing standalone applications. Instead, it provides facilities to embed a Python interpreter and focuses on making it easy to expose parts of the application to Python.

PythonQt makes extensive use of the Qt 4 meta-object system. Thus, PythonQt can dynamically script any QObject without any prior knowledge of it, using only the information supplied by QMetaObject (defined using Qt's moc tool in C++ applications). This dynamic approach allows several different script bindings to be embedded in the same application, each of which can use the same scripting API; e.g., JavaScript (QSA or QtScript) and Python.

The following sections will highlight some core features of PythonQt. For a detailed description of PythonQt's features and more sophisticated examples, visit the project's website.

Getting started

The following example shows the steps that are needed to integrate PythonQt with your Qt Application.

    #include "PythonQt.h"
    #include <QApplication>
 
 
    int main(int argc, char **argv)
    {
        QApplication qapp(argc, argv);
 
        PythonQt::init();
        PythonQtObjectPtr mainModule = 
                          PythonQt::self()->getMainModule();
        QVariant result = mainModule.evalScript(
                        mainModule, "19*2+4", Py_eval_input);
        return 0;
    }

We first initialize a PythonQt singleton, which in turn initializes the Python interpreter itself. We then obtain a smart pointer (PythonQtObjectPtr) to Python's __main__ module (this is where the scripts will be run) and evaluate some Python code in this module.

The result variable in this case will contain 42, evaluated by Python.

Creating an Application Scripting API

The art of application scripting is to find the level of detail for the API of your application that best suits your users, developers and support staff. Basically, you invent a domain-specific language for your application that makes it easy for your users to access exactly the features they want, without needing to have a C++ compiler to hand.

A typical use case of PythonQt is to expose a single application object to Python and then let the users, developers or support staff create small scripts to change aspects of your applications via scripting.

Typically, you will create a new QObject-derived API class and use it as an adapter to various other classes in your application. You may also expose any existing QObjects of your application directly, but normally this exposes too many details to the script users, and it forces you to keep the interfaces of all exposed classes stable---otherwise your user's scripts will break or behave in unexpected ways.

Creating specialized API objects is often the preferred solution, making it easier to keep a stable interface and to document what parts of the application a script can access. PythonQt supports all QVariant types, so you can create rich application APIs that return simple types such as QDateTime and QPixmap, or even hierarchical QVariantMap objects containing arbitrary QVariant values.

About Python

Python (http://www.python.org) is an object-oriented programming language with a growing user community and a huge set of standard modules.

Python was designed to be "extended and embedded" easily. Libraries written in C and C++ can be wrapped for use by Python programs, and the interpreter can be embedded into applications to provide scripting services.

There are several well known applications which support Python scripting:

GUI Scripting

Let's consider a simple example in which we create a small Qt user interface containing a group box, which we expose to Python under the name "box".

Pythonqt-Gui

The C++ code to define the user interface looks like this:

    QGroupBox *box = new QGroupBox;
    QTextBrowser *browser = new QTextBrowser(box);
    QLineEdit *edit = new QLineEdit(box);
    QPushButton *button = new QPushButton(box);
 
    button->setObjectName("button1");
    edit->setObjectName("edit");
    browser->setObjectName("browser");
 
    mainModule.addObject("box", box);

Now let us create a Python script that uses PythonQt to access the GUI. Firstly, we can see how easy it is to access Qt properties and child objects:

    <span class="comment"># Set the title of the group box via the title property.</span>
    box.title = <span class="string">'PythonQt Example'</span>
 
    <span class="comment"># Set the HTML content of the QTextBrowser.</span>
 
    box.browser.php = <span class="string">'Hello <b>Qt</b>!'</span>
 
    <span class="comment"># Set the title of the button.</span>
    box.button1.text = <span class="string">'Append Text'</span>
 
    <span class="comment"># Set the line edit's text.</span>
    box.edit.text = <span class="string">'42'</span>

Note that each Python string is automatically converted to a QString when it is assigned to a QString property.

Signals from C++ objects can be connected to Python functions. We define a normal function, and we connect the button's clicked() signal and the line edit's returnPressed() signal to it:

    <span class="keyword">def</span> <span class="function">appendLine</span>():
        box.browser.<span class="function">append</span>(box.edit.text)
 
    box.button1.<span class="function">connect</span>(<span class="string">'clicked()'</span>, appendLine)
    box.edit.<span class="function">connect</span>(<span class="string">'returnPressed()'</span>, appendLine)

The group box is shown as a top-level widget in the usual way:

    box.<span class="function">show</span>()

To evaluate the above script, we need to call a special function in the main module. Here, we have included the script as a file in Qt's resource system, so we specify it with the usual ":" prefix:

    mainModule.<span class="function">evalFile</span>(<span class="string">":GettingStarted.py"</span>);

Now, whenever you press return in the line edit or click the button, the text from the line edit is appended to the text in the browser using the Python appendLine() function.

The PythonQt Module

Scripts often need to do more than just process data, make connections, and call functions. For example, it is usually necessary for scripts to be able to create new objects of certain types to supply to the application.

To meet this need, PythonQt contains a Python module named PythonQt which you can use to access constructors and static members of all known objects. This includes the QVariant types and the Qt namespace.

Here are some example uses of the PythonQt module:

    <span class="keyword">from</span> PythonQt <span class="keyword">import</span> *
 
    <span class="comment"># Access enum values of the Qt namespace.</span>
 
    <span class="keyword">print</span> <span class="class">Qt</span>.AlignLeft
 
    <span class="comment"># Access a static QDate method.</span>
    <span class="keyword">print</span> <span class="class">QDate</span>.<span class="function">currentDate</span>()
 
    <span class="comment"># Construct a QSize object</span>
    a = <span class="class"><span class="function">QSize</span></span>(1,2)

Decorators and C++ Wrappers

A major problem that comes with the dynamic approach of PythonQt is that only slots are callable from Python. There is no way to do dynamic scripting on C++ methods because Qt's meta-object compiler (moc) does not generate run-time information for them.

PythonQt introduces the concept of the "decorator slot", which reuses the mechanism used to dynamically invoke Qt slots to support constructors, destructors, static methods and non-static methods in a very straightforward way. The basic idea is to introduce new QObject-derived "decorator objects" (not to be confused with Python's own decorators) whose slots follow the decorator naming convention and are used by PythonQt to make, for example, normal constructors callable.

This allows any C++ or QObject-derived class to be extended with additional members anywhere in the existing class hierarchy.

The following class definition shows some example decorators:

    class PyExampleDecorators : public QObject
    {
        Q_OBJECT
 
    public slots:
        QVariant new_QSize(const QPoint &p)
            { return QSize(p.x(), p.y()); }
 
        QPushButton *new_QPushButton(const QString &text,
                                     QWidget *parent = 0)
            { return new QPushButton(text, parent); }
 
        QWidget *static_QWidget_mouseGrabber()
            { return QWidget::mouseGrabber(); }
 
        void move(QWidget *w, const QPoint &p) { w->move(p); }
        void move(QWidget *w, int x, int y) { w->move(x, y); }
    };

After registering the above example decorator with PythonQt (via PythonQt::addDecorators()), PythonQt now supplies:

  • A QSize variant constructor that takes a QPoint.
  • A constructor for QPushButton that takes a string and a parent widget.
  • A new static mouseGrabber() method for QWidget.
  • An additional slot for QWidget, making move() callable. (move() is not a slot in QWidget.)
  • An additional slot for QWidget, overloading the above move() method.

The decorator approach is very powerful, since it allows new functionality to be added anywhere in your class hierarchy, without the need to handle argument conversion manually (e.g., mapping constructor arguments from Python to Qt). Making a non-slot method available to PythonQt becomes a one-line statement which can just be a case of forwarding the call to C++.

Other Features

PythonQt also provides a number of other "advanced" features that we don't have space to cover here. Some of the most interesting are:

  • Wrapping of non-QObject-derived C++ objects.
  • Support for custom QVariant types.
  • An interface for creating your own import replacement so that, for example, Python scripts can be signed/verified before they are executed.

The examples supplied with PythonQt cover many of the additional features we have not addressed in this article.

Future Directions

PythonQt was written to make MeVisLab scriptable, and has now reached a satisfactory level of stability. It makes it very easy to embed Python scripting support into existing Qt applications that do not require the extensive coverage of the Qt API that PyQt provides.

I would like to thank my company MeVis Research, who made it possible for me to release PythonQt as an open source project on SourceForge, licensed under the GNU Lesser General Public License (LGPL). See the project's home page at pythonqt.sourceforge.net for more information.

I am looking for more developers to join the project. Please contact me at florian (at) mevis.de if you would like to contribute!

The full source code for the examples shown in this article is available as part of the PythonQt package, available from SourceForge.