The Role of Language Bindings: Pythonizing Qt


Dr. Cristián Maureira-Fredes
@cmaureir

Why Akademy?

blogs.kde.org/2010/10/27/tale-chile/

Chilean Linux Meeting 2010

btw I use arch since 2009

Disclaimer

Qt Language Bindings

Exposing Qt to other languages is nothing new.

Qt Language Bindings

Binding experience, is also Qt experience.

Name Language Latest version Last commit License Company
PySide Python 6.7.2 - LGPLv3/Commercial The Qt Company
PyQt Python 6.7.2 - GPLv3/Commercial Riverbank Computing
CXX-Qt Rust 6.x* - MIT/Apache 2.0 KDAB
qmetaobject-rs Rust 6.5.0 2024.08.07 MIT Woboq
QtJambi Java/Kotlin 6.7.2 2024.06.25 LGPLv2 Omix Visualization
QML-zig Zig 5.15.2 2024.08.11 Apache 2.0 -
QML.jl Julia 6.5.2 2024.07.12 MIT JuliaGraphs
RingQt Ring 5.15.15 2024.09.02 MIT -
NodeGui Node.js 6.6.0 2024.05.31 MIT NodeGui
nimqt Nim 6.4.3 2024.01.27 GPL2 -

(updated on Sep 2nd, 2024)

Other 26 projects (some inactive) in wiki.qt.io/Language_Bindings

Most bindings

expose only 1:1 API

🤔

What about improving

the Qt ecosystem?

...or at least offering

a little bit more.

The case of

Python

StackOverflow Survey 2024 Results

You can understand

why Python with a few things

Python's syntax


        #ifndef MAINWINDOW_H
        #define MAINWINDOW_H

        #include <QMainWindow>
        #include <QPushButton>

        class MainWindow : public QMainWindow
        {
            Q_OBJECT
            public:
                MainWindow(QWidget *parent = nullptr);
            private slots:
                void handleButton();
            private:
                QPushButton *m_button;
        };

        #endif // MAINWINDOW_H

        #include "mainwindow.h"

        MainWindow::MainWindow(QWidget *parent)
           : QMainWindow(parent)
        {
            m_button = new QPushButton("My Button", this);
            connect(m_button, SIGNAL(clicked()), this,
              SLOT(handleButton()));
        }

        void MainWindow::handleButton()
        {
            m_button->setText("Ready");
        }

        #include <QApplication>
        #include "mainwindow.h"

        int main(int argc, char *argv[])
        {
            QApplication app(argc, argv);
            MainWindow mainWindow;
            mainWindow.show();
            return app.exec(d);
        }

      // don't forget the CMakeLists.txt
      

        import sys
        from PySide6.QtCore import Slot
        from PySide6.QtWidgets import (
          QApplication, QMainWindow, QPushButton
        )

        class MainWindow(QMainWindow):
            def __init__(self, parent=None):
                QMainWindow.__init__(self, parent)
                self.b = QPushButton("My Button", self)
                self.b.clicked.connect(self.handle_button)

            @Slot()
            def handle_button(self):
                self.b.setText("Ready")

        if __name__ == "__main__":
            app = QApplication(sys.argv)
            mainWindow = MainWindow()
            mainWindow.show()
            sys.exit(app.exec())

Python's heterogeneity

Web Development
Data
Science

Embedded systems

Python: package distribution

qtpip

qt.io/blog/qtpip-qtforpython-installer

Machine Learning popularity

We got the perfect recipe 🏆

A Python user-facing experience

with a C++ core.

What about...
Let's go back to Python and Qt

PyQt & PySide

🍿🍿🍿🍿🍿🍿🍿🍿🍿

PySide

  • LGPLv3/Commercial
  • Developed by The Qt Company
  • Support Windows, macOS and Linux.
  • Support for Android + Deployment
  • Exposes Qt API and more.

PyQt

  • GPLv3/Commercial
  • Developed by Riverbank Co.
  • Support Windows, macOS and Linux.
  • Support for Android + Deployment
  • Exposes Qt API.

The story of PySide

For PySide

exposing the Qt API (like PyQt)

is not enough

Searching for new

features

1. Embedding the Python Interpreter

scriptableapplication example

2. NumPy support

numpy.org The fundamental package for scientific computing with Python


>>> from PySide6.QtGui import QMatrix4x4
>>> import numpy as np
>>> m = np.eye(4)
>>> m.shape = 16
>>> m
array([[1., 0., 0., 0.],
       [0., 1., 0., 0.],
       [0., 0., 1., 0.],
       [0., 0., 0., 1.]])
>>> QMatrix4x4(m)
PySide6.QtGui.QMatrix4x4((1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1))
      

// Also ad-hoc API
QPainter.drawPointsNp(PyArrayObject *x, PyArrayObject *y);

QXYSeries.appendNp(PyArrayObject *x, PyArrayObject *y);
QXYSeries.replaceNp(PyArrayObject *x, PyArrayObject *y);
      

3. Qt Creator support

doc.qt.io/qtcreator/creator-python-development.html

That was not enough 😒

Adding Python-ish

features

4. snake_case


      # Common Qt structure
      # - Using setter/getter


      table = QTableWidget()
      table.setColumnCount(2)

      button = QPushButton("Add")
      button.setEnabled(False)

      layout = QVBoxLayout()
      layout.addWidget(table)
      layout.addWidget(button)
      

It might look strange to you, C++ developer.

5. True properties


      # Common Qt structure
      # - Using setter/getter
      # - No writable properties

      table = QTableWidget()
      table.setColumnCount(2)

      button = QPushButton("Add")
      button.setEnabled(False)

      layout = QVBoxLayout()
      layout.addWidget(table)
      layout.addWidget(button)
      

This was the #1 requested feature

6. QmlElement


from PySide6.QtQml import QmlElement
# ...

QML_IMPORT_NAME = "io.qt.some_name"
QML_IMPORT_MAJOR_VERSION = 1


@QmlElement
class Bridge(QObject):

    @Slot(str, result=str)
    def getColor(self, s):
        # ...
    

7. Tooling


    # Wrappers
    pyside6-assistant
    pyside6-designer
    pyside6-linguist
    pyside6-lrelease
    pyside6-lupdate
    pyside6-qml
    pyside6-qmlcachegen
    pyside6-qmlimportscanner
    pyside6-qmllint
    pyside6-qmlsc
    pyside6-qmltyperegistrar
    pyside6-rcc
    pyside6-uic
    

    # Utilities
    pyside6-genpyi
    pyside6-metaobjectdump
    

    # New tools
    pyside6-project
    pyside6-deploy
    pyside6-android-deploy
    

doc.qt.io/qtforpython-6/tools/index.html

Moaaaaar features!

8. Android support

9. QtAsyncio

QtAsyncio


# ...
import sys
import asyncio
import PySide6.QtAsyncio as QtAsyncio

class MainWindow(QMainWindow):
  def __init__(self):
    super().__init__()
    widget = QWidget()
    self.setCentralWidget(widget)
    layout = QVBoxLayout(widget)
    self.text = QLabel("The answer is 42.")
    layout.addWidget(self.text, alignment=Qt.AlignmentFlag.AlignCenter)

    async_trigger = QPushButton(text="What is the question?")
    async_trigger.clicked.connect(lambda: asyncio.ensure_future(self.set_text()))
    layout.addWidget(async_trigger, alignment=Qt.AlignmentFlag.AlignCenter)

  async def set_text(self):
    await asyncio.sleep(1)
    self.text.setText("What do you get if you multiply six by nine?")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()

    QtAsyncio.run(handle_sigint=True)

10. Qt Design Studio


% tree Python
Python
├── autogen
│   └── settings.py
└── main.py

2 directories, 2 files
      

11. Lazy loading

12. QtScript...but Python


static Demo demos[] = {
    {"Main Window",
     R"(# main_win is bound from C++
print("name=", main_win.objectName)
# point is bound from C++
print("point.x,y=", point.x(), point.y())

# Play with properties
main_win.objectName = "TestName"
print("changed name=", main_win.objectName)

# Create a point in Python
python_point = QPoint()
print("python_point.x,y=", python_point.x(),
    python_point.y())
python_point.setX(500)
python_point.setY(500)
)"},
};
  

13. Flatpak support

...since we are talking

about KDE


Merge request: Add Python bindings

Is that enough 😰?

(

Re-writing Python code into a compiled language is faster

A different-approach

Codon

docs.exaloop.io/codon

)

The future of the project

a. Simplifying User-facing experience


import kimyou
from kimyou import Window, TextField, Button

def main(window: Window):
    window.title = "Testing title"
    window.layout_direction = "vertical"
    name = TextField(value="Enter your name")
    button = Button(text="Click me!",
        on_click=lambda: print(f"Hello {name.value}"))
    button2 = Button(text="Exit", on_click=window.exit)
    # Vertical
    #window.add(name)
    #window.add(button)
    #window.add(button2)
    # Horizontal
    window.add([name, button, button2])

kimyou.app(target=main)
  

b. Packaging and Tooling

c. New module integration

d. New Python-only Qt modules

And other features

requested by usersyou

What's next?

Embrace the idea of a

many-language framework

Be boring and

see what works in other languages

Imagine everything we can

achieve together

A better (and polyglot) Qt framework

means a better KDE

Q&A

The Role of Language Bindings: Pythonizing Qt


Dr. Cristián Maureira-Fredes
@cmaureir