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.
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.
PySide2
The Qt bindings themselves are easy to install and to 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.
Getting started
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.
Precondition
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.
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.
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.
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
Build
Now that we have prepared everything, the actual build is easy.
# 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.
Verify our build
Try it by running the shiboken2 command line tool:
(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 your library with Shiboken2
Note: All files mentioned in this section can be cloned at GitHub. Clone the repository!
Shiboken2 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 for.
A type description XML file which (at a minimum) tells shiboken which types to wrap.
The library
Our trivial library contains only a single QObject with signals, slots and a Q_ENUM which is used in the API. Checkout the source code for implementation details.
qobjectwithenum.h:
#pragma once
#include
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:
#include "qobjectwithenum.h"
#include
#include
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");
}
The ‘entry-point’ header file
Our ‘entry-point’ 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:
#define QT_ANNOTATE_ACCESS_SPECIFIER(a) __attribute__((annotate(#a)))
#include "qobjectwithenum.h"
The typesystem description
Although the typesystem description XML file can do much more than just describe which types to expose, we will stick to a rather simple version within our example.
bindings.xml:
<?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>
Generate the bindings
Note: 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.
CMakeLists.txt:
cmake_minimum_required(VERSION 3.12)
cmake_policy(VERSION 3.12)
find_package(Qt5 REQUIRED Core)
get_target_property(QtCore_location Qt5::Core LOCATION)
get_filename_component(QtCore_libdir ${QtCore_location} DIRECTORY)
set(CMAKE_AUTOMOC ON)
project(Shiboken2-Qt-Example)
...
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 git repository.
Make a shadow build and install
mkdir build
cd build
cmake ..
make install
Using the module
Now that the bindings have been generated and built we can write a python script which uses the new binding module.
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
$ workon pyside2build
(pyside2build)$ python main.py
The expected output is:
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. If you have any question, just leave a comment down below.
7 Responses
Hi, I cannot build your example under Windows 10 64bit. My toolkits are: MSVC 2017, Qt 5.12.3 msvc 2017 64bit, Python 2.7.3 64bit. The error is: fatal error LNK1107: file invalid: cannot read at 0x320
Could you provide me any hints?
Hi,
the error you posted is unfortunately not enough information and I do not have a similar setup available at the moment to reproduce the issue. Can you paste the whole build output somewhere?
Hi, I have the same error on Windows, everything works fine on Linux, maybe you know what the reason could be?
qobjectwithenum.cpp
[ 44%] Linking CXX shared library libexamplebinding.dll
LINK: command “C:\PROGRA~2\MICROS~3\2017\COMMUN~1\VC\Tools\MSVC\1416~1.270\bin\Hostx64\x64\link.exe /nologo @CMakeFiles\libexamplebinding.dir\objects1.rsp /out:libexamplebinding.dll /implib:libexamplebinding.lib /pdb:K:\project\env\pysi\pyside-setup\examples\Example_Qt\build\libexamplebinding.pdb /dll /version:0.0 /machine:x64 /INCREMENTAL:NO C:\Qt\5.12.9\msvc2017\bin\Qt5Core.dll kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib /MANIFEST /MANIFESTFILE:libexamplebinding.dll.manifest” failed (exit code 1107) with the following output:
C:\Qt\5.12.9\msvc2017\bin\Qt5Core.dll : fatal error LNK1107: invalid or corrupt file: cannot read at 0x2D8
Where’s the typesystem_core.xml file?
It’s part of shiboken. When you run shiboken you can set –typesystem-paths=
According to here https://pyside.qt-project.narkive.com/dFHodgAf/shiboken-can-t-find-typesystem-core-xml
You may not use relative pathes.
Thank you very much – this already helped a lot!
But how to link against additional Qt modules like Qt::WebSockets or Qt::Quick? I’m not vey familiar with CMake .. so I’m a bit lost.
You can add the modules you need to the `find_packages` (e.g. `find_package(Qt5 REQUIRED Core WebSockets Quick)`) and `target_link_libraries` (e.g. `
target_link_libraries(${bindings_library} PRIVATE Qt5::WebSockets Qt5::Quick)`)