With the release of Qt 5.12, Qt for Python is officially supported and can be used to write full-fledged Qt applications using Python as the main programming language (https://blog.qt.io/blog/2018/12/18/qt-python-5-12-released/).
This prompted us to also take a closer look at the bindings (the Python module is called PySide2) and also the underlying technology, namely the binding generator called Shiboken2.
This post will show you how to create a set of Python bindings for a small Qt library from scratch.
PySide2
The Qt bindings themselves are easy to install and use.
If you have a python interpreter and the pip package manager set up on your system, whether it is Windows or Linux, you can install the module via pip install PySide2 and are ready to go.
Shiboken2
While it is nice to use the Qt framework from Python it is even better to be able to quickly create Python bindings for your own Qt libraries and use their API from Python with relatively little work.
Setup
All examples on this article have been run on Ubuntu 18.04, the setup steps for other platforms might vary.
Since the most recent documentation advises to build Shiboken2 from source for building your own bindings, we will do that now.
Install Qt 5.12
This posts assumes that you have a Qt 5.12 installation ready at $HOME/Qt/5.12.0/gcc_64/, which is the default location when using the online installer from qt.io.
If you use Qt from a different location adjust the paths accordingly.
Install all required tools and libraries
The following apt command line works for Ubuntu 18.04 and should be a good starting point for other distributions.
1 2 3 4 5 6 |
sudo apt update sudo apt install llvm-6.0 virtualenvwrapper python3 python3-dev cmake build-essential git clang-6.0 libclang-6.0-dev libxslt-dev mesa-common-dev libgl1-mesa-glx libglib2.0-0 wget # We require CMake >= 3.12 due to the improved Python support wget https://github.com/Kitware/CMake/releases/download/v3.13.2/cmake-3.13.2-Linux-x86_64.tar.gz tar xvf cmake-3.13.2-Linux-x86_64.tar.gz export PATH=$PWD/cmake-3.13.2-Linux-x86_64/bin/:$PATH |
Get the sourcecode
The fastest way to get the PySide2 and Shiboken2 source is to clone the git repository.
1 2 3 4 |
git clone git://code.qt.io/pyside/pyside-setup.git cd pyside-setup git checkout 5.12.0 git submodule update --init |
Set up a python virtualenv
We advise you to work in python virtual environments, as that will keep your system installation clean and you should be able to do anything else in this post without requiring root privileges.
1 2 |
source /etc/bash_completion.d/virtualenvwrapper mkvirtualenv -p `which python3` pyside2build |
This will set up a python3 virtual environment in your home directory (~/.local/share/virtualenvs/pyside2build) and activate it. Your shell should have a prefix indicating that. To activate the virtualenv in the future use workon pyside2build.
Set up the environment
The API extractor of Shiboken2 requires a clang/llvm version newer than the default version in Ubuntu 18.04, and we need to tell the buildsystem where to find it.
1 2 3 4 5 |
export LLVM_INSTALL_DIR=`llvm-config-6.0 --prefix` # export the Qt library path so the correct icu libraries are found export LD_LIBRARY_PATH=$HOME/Qt/5.12.0/gcc_64/lib/ # Put correct the Qt binaries in the PATH export PATH=$HOME/Qt/5.12.0/gcc_64/bin/:$PATH |
Build
Now that we have prepared everything, the actual build is easy.
1 2 3 4 |
# Change into your clone of pyside-setup.git cd pyside-setup # Adjust the jobs parameter for the number of cores on your system python setup.py install --qmake=$HOME/Qt/5.12.0/gcc_64/bin/qmake --jobs=8 |
After the build has finished, which can take some time, you should have a fully working and properly linked PySide2 and Shiboken2 installation in your virtualenv.
Try it by running the shiboken2 command line tool:
1 2 |
(pyside2build) swid@spica:~/src/pyside-setup (5.12)$ shiboken2 shiboken: Required argument header-file is missing.Command line: |
If you see output similar to this, you are good to go.
Wrapping a library
Note: All files mentioned in this section can be downloaded as a tarball
The Shiboken2 post on qt.io introduces the process quite well, so you can also take a look there to better understand the process: https://blog.qt.io/blog/2018/05/31/write-python-bindings/
Shiboken2 input
Shiboken requires mainly three things to work
- The library to generate bindings for
- An ‘entry-point‘ header file which includes all other headers of classes for which bindings are generated
- A type description XML file which (at a minimum) tells shiboken which types to wrap
Note that the the typesystem description can do much more than describe which types to expose using which semantics: https://doc.qt.io/qtforpython/shiboken2/contents.html
The library
Our trivial library contains only a single QObject with signals, slots and a Q_ENUM which is used in the API.
qobjectwithenum.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#pragma once #include <QObject> class QObjectWithEnum : public QObject { Q_OBJECT public: enum class MyEnum { Some, Values, For, The, Enum, }; Q_ENUM(MyEnum) explicit QObjectWithEnum(QObject *parent = nullptr); QString nonSlotFunction(const MyEnum value) const; signals: void someSignal(QString stringArg); public slots: void aSlot(); }; |
qobjectwithenum.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include "qobjectwithenum.h" #include <QDebug> #include <QMetaEnum> QObjectWithEnum::QObjectWithEnum(QObject *parent) : QObject(parent) { } QString QObjectWithEnum::nonSlotFunction(const QObjectWithEnum::MyEnum value) const { const auto ret = metaObject()->enumerator(0).valueToKey(int(value)); qDebug() << __FUNCTION__ << "returning:" << ret; return ret; } void QObjectWithEnum::aSlot() { qDebug() << __FUNCTION__ << "slot called"; emit someSignal("from aSlot"); } |
See the attached project for implementation details.
The glue header
Our glue header pulls in the required library headers, which is pretty simple in our case, and also sets up a define required for generating code for the Qt specialities.
bindings.h:
1 2 3 |
#define QT_ANNOTATE_ACCESS_SPECIFIER(a) __attribute__((annotate(#a))) #include "qobjectwithenum.h" |
The typesystem description
In our case this is also pretty simple:
bindings.xml:
1 2 3 4 5 6 7 |
<?xml version="1.0"?> <typesystem package="Shiboken2QtExample"> <load-typesystem name="typesystem_core.xml" generate="no"/> <object-type name="QObjectWithEnum"> <enum-type name="MyEnum" /> </object-type> </typesystem> |
As I wrote before, the typesystem description is a powerful tool but out of the scope of this particular post.
Building the bindings
Note: Due to the greatly improved Python integration CMake >= 3.12 is required here.
To simplify building and linking all the parts we adapt a CMakeLists.txt file from the sample bindings example.
The example CMakeLists.txt installs the resulting library into the source folder and adds all relevant paths as RPATHS for the built libraries which makes local usage convenient.
This is not the right thing to do for production projects, consult your local system integrator :-).
CMakeLists.txt
The CMake build file has been adapted from the official sample binding. The content is not relevant to this article. It can be found in the downloadable zip.
Make a shadow build and install
1 2 3 4 |
mkdir build cd build cmake .. make install |
Using the module
Once the bindings have been generated and built we can now write a python script which uses the binding module.
1 2 3 4 5 6 7 |
from Shiboken2QtExample import * a = QObjectWithEnum() a.someSignal.connect(lambda x: print("Signal emitted: %s" % x)) a.aSlot() print("int(QObjectWithEnum.MyEnum.Values) =", int(QObjectWithEnum.MyEnum.Values)) a.nonSlotFunction(QObjectWithEnum.MyEnum.Some) |
The easiest way to use the wrapped library is to activate the virtual environment, which contains the correct PySide2 version, and run it
1 2 3 4 5 6 |
swid@spica:~/pyside-example/$ workon pyside2build (pyside2build) swid@spica:~/pyside-example/$ python main.py aSlot slot called Signal emitted: from aSlot int(QObjectWithEnum.MyEnum.Values) = 1 nonSlotFunction returning: Some |
That’s it. Zero to Qt library bindings in one blog post.
basysKom is offering consulting, training and development services for Qt. We have recently added a Qt for Python module to our training. If you are interested get in contact with info@basyskom.com.