PyQt/Pyside - Development of desktop applications

онлайн тренажер по питону
Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction

PyQt and PySide are powerful libraries for creating cross‑platform graphical user interfaces in Python using the Qt framework. They provide access to Qt’s rich toolkit: widgets, event systems, styles, animations, database integration, and even QML for building modern interfaces.

The main difference between PyQt and PySide lies in licensing: PyQt requires a GPL or commercial license, while PySide (the official implementation from the Qt Company) is released under the more permissive LGPL, making it preferable for commercial projects.

In this comprehensive guide we will cover every aspect of working with these libraries: from installation and building simple interfaces to complex projects with databases, styling, and packaging ready‑to‑run applications.

Installation and Setup

Installing PyQt5 and PyQt6

# PyQt5 (stable version)
pip install pyqt5 pyqt5-tools

# PyQt6 (modern version)
pip install pyqt6 pyqt6-tools

Installing PySide2 and PySide6

# PySide2 (for Qt 5)
pip install pyside2

# PySide6 (recommended version)
pip install pyside6

Verifying the Installation

# For PyQt5
from PyQt5.QtWidgets import QApplication
print("PyQt5 installed successfully")

# For PySide6
from PySide6.QtWidgets import QApplication
print("PySide6 installed successfully")

Basics of Application Development

Simple Window with a Button

PyQt5 version

from PyQt5.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget
import sys

class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('My First PyQt5 Application')
        self.setGeometry(100, 100, 300, 200)
        
        layout = QVBoxLayout()
        button = QPushButton('Press Me')
        button.clicked.connect(self.on_click)
        layout.addWidget(button)
        
        self.setLayout(layout)
    
    def on_click(self):
        print("Button pressed!")

app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())

PySide6 version

from PySide6.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget
import sys

class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('My First PySide6 Application')
        self.setGeometry(100, 100, 300, 200)
        
        layout = QVBoxLayout()
        button = QPushButton('Press Me')
        button.clicked.connect(self.on_click)
        layout.addWidget(button)
        
        self.setLayout(layout)
    
    def on_click(self):
        print("Button pressed!")

app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())

Qt Application Architecture

Main Components

QApplication – the central object of any Qt application. It manages the event loop, processes system events, and coordinates all widgets.

QWidget – the base class for all UI elements. It can contain other widgets and act as a container.

QMainWindow – a specialized widget for creating the main application window with support for menus, toolbars, status bars, and a central widget.

QDialog – class for creating dialog windows, including modal and modeless dialogs.

Application Lifecycle

  1. Create a QApplication instance
  2. Instantiate and configure widgets
  3. Show the main window
  4. Start the event loop with exec() or exec_()
  5. Terminate the application

Signals and Slots System

Core Principles

Signals and slots are Qt’s mechanism for handling events and inter‑object communication. A signal is emitted when an event occurs, and a slot is a function that handles it.

Connecting Signals

# Simple connection
button.clicked.connect(self.on_button_clicked)

# Connection with parameters
button.clicked.connect(lambda: self.process_data("parameter"))

# Connecting to a built‑in slot
button.clicked.connect(self.close)

# Disconnecting a signal
button.clicked.disconnect()

Creating Custom Signals

from PyQt5.QtCore import pyqtSignal, QObject

class DataProcessor(QObject):
    # Define custom signals
    data_processed = pyqtSignal(str)
    progress_updated = pyqtSignal(int)
    
    def process_data(self):
        # Emit signals with data
        self.progress_updated.emit(50)
        self.data_processed.emit("Processing complete")

Widgets and UI Components

Fundamental Input Widgets

from PyQt5.QtWidgets import *
from PyQt5.QtCore import Qt

class InputDemo(QWidget):
    def __init__(self):
        super().__init__()
        layout = QVBoxLayout()
        
        # Text fields
        self.line_edit = QLineEdit("Single‑line input")
        self.text_edit = QTextEdit("Multi‑line input")
        
        # Numeric fields
        self.spin_box = QSpinBox()
        self.spin_box.setRange(0, 100)
        
        # Date and time selectors
        self.date_edit = QDateEdit()
        self.time_edit = QTimeEdit()
        
        # Checkboxes and radio buttons
        self.checkbox = QCheckBox("Enable option")
        self.radio1 = QRadioButton("Option 1")
        self.radio2 = QRadioButton("Option 2")
        
        # Combo box
        self.combo_box = QComboBox()
        self.combo_box.addItems(["Item 1", "Item 2", "Item 3"])
        
        # Slider
        self.slider = QSlider(Qt.Horizontal)
        self.slider.setRange(0, 100)
        
        # Add all widgets to the layout
        for widget in [self.line_edit, self.text_edit, self.spin_box, 
                      self.date_edit, self.time_edit, self.checkbox, 
                      self.radio1, self.radio2, self.combo_box, self.slider]:
            layout.addWidget(widget)
        
        self.setLayout(layout)

Data Display Widgets

class DisplayDemo(QWidget):
    def __init__(self):
        super().__init__()
        layout = QVBoxLayout()
        
        # Labels
        self.label = QLabel("Simple label")
        self.rich_label = QLabel("<b>Bold</b> <i>Italic</i> <u>Underlined</u>")
        
        # Progress bar
        self.progress = QProgressBar()
        self.progress.setValue(75)
        
        # Table
        self.table = QTableWidget(3, 2)
        self.table.setHorizontalHeaderLabels(["Column 1", "Column 2"])
        
        # List
        self.list_widget = QListWidget()
        self.list_widget.addItems(["Item 1", "Item 2", "Item 3"])
        
        # Tree view
        self.tree = QTreeWidget()
        self.tree.setHeaderLabels(["Name", "Value"])
        
        for widget in [self.label, self.rich_label, self.progress, 
                      self.table, self.list_widget, self.tree]:
            layout.addWidget(widget)
        
        self.setLayout(layout)

Complete Methods and Functions Table

Component Method / Property Description Example Usage
QWidget setWindowTitle() Sets the window title widget.setWindowTitle("My Window")
  setGeometry() Window size and position widget.setGeometry(100, 100, 800, 600)
  show() Displays the widget widget.show()
  hide() Hides the widget widget.hide()
  close() Closes the widget widget.close()
  setEnabled() Enables / disables the widget widget.setEnabled(False)
  setStyleSheet() Applies CSS‑like styles widget.setStyleSheet("color: red;")
QPushButton clicked Click signal button.clicked.connect(func)
  setText() Sets button text button.setText("New Text")
  setIcon() Sets an icon button.setIcon(QIcon("icon.png"))
  setCheckable() Makes the button checkable button.setCheckable(True)
QLineEdit textChanged Signal emitted when text changes edit.textChanged.connect(func)
  text() Returns the current text text = edit.text()
  setText() Sets the text edit.setText("New Text")
  setPlaceholderText() Placeholder text edit.setPlaceholderText("Enter...")
  setValidator() Assigns a validator edit.setValidator(QIntValidator())
QLabel setText() Sets label text label.setText("Text")
  setPixmap() Sets an image label.setPixmap(QPixmap("image.png"))
  setAlignment() Aligns the content label.setAlignment(Qt.AlignCenter)
QComboBox addItem() Adds a single item combo.addItem("Item")
  addItems() Adds multiple items combo.addItems(["1", "2", "3"])
  currentText() Returns the selected text text = combo.currentText()
  currentIndexChanged Signal emitted when the index changes combo.currentIndexChanged.connect(func)
QTableWidget setRowCount() Sets number of rows table.setRowCount(10)
  setColumnCount() Sets number of columns table.setColumnCount(5)
  setItem() Places an item in a cell table.setItem(0, 0, QTableWidgetItem("Text"))
  setHorizontalHeaderLabels() Sets column headers table.setHorizontalHeaderLabels(["Col1", "Col2"])
QListWidget addItem() Adds a single list item list.addItem("Item")
  addItems() Adds multiple list items list.addItems(["1", "2", "3"])
  currentRow() Returns the currently selected row row = list.currentRow()
  itemClicked Signal emitted when an item is clicked list.itemClicked.connect(func)
QApplication exec_() / exec() Starts the event loop app.exec_()
  quit() Exits the application app.quit()
  processEvents() Processes pending events app.processEvents()

Interface Layouts

Primary Layout Types

# Vertical layout
v_layout = QVBoxLayout()
v_layout.addWidget(widget1)
v_layout.addWidget(widget2)

# Horizontal layout
h_layout = QHBoxLayout()
h_layout.addWidget(widget1)
h_layout.addWidget(widget2)

# Grid layout
grid_layout = QGridLayout()
grid_layout.addWidget(widget1, 0, 0)  # row 0, column 0
grid_layout.addWidget(widget2, 0, 1)  # row 0, column 1
grid_layout.addWidget(widget3, 1, 0, 1, 2)  # row 1, spanning columns 0‑1

# Form layout
form_layout = QFormLayout()
form_layout.addRow("Name:", QLineEdit())
form_layout.addRow("Email:", QLineEdit())

Nested Layouts

class ComplexLayout(QWidget):
    def __init__(self):
        super().__init__()
        
        # Main vertical layout
        main_layout = QVBoxLayout()
        
        # Top horizontal bar
        top_layout = QHBoxLayout()
        top_layout.addWidget(QLabel("Header"))
        top_layout.addWidget(QPushButton("Settings"))
        
        # Central grid area
        grid_layout = QGridLayout()
        grid_layout.addWidget(QLabel("Field 1:"), 0, 0)
        grid_layout.addWidget(QLineEdit(), 0, 1)
        grid_layout.addWidget(QLabel("Field 2:"), 1, 0)
        grid_layout.addWidget(QLineEdit(), 1, 1)
        
        # Bottom button panel
        button_layout = QHBoxLayout()
        button_layout.addWidget(QPushButton("OK"))
        button_layout.addWidget(QPushButton("Cancel"))
        
        # Assemble all layouts
        main_layout.addLayout(top_layout)
        main_layout.addLayout(grid_layout)
        main_layout.addLayout(button_layout)
        
        self.setLayout(main_layout)

Interface Styling

Qt Style Sheets (CSS‑like)

# Styling an individual widget
button.setStyleSheet("""
    QPushButton {
        background-color: #4CAF50;
        border: none;
        color: white;
        padding: 15px 32px;
        text-align: center;
        font-size: 16px;
        border-radius: 4px;
    }
    QPushButton:hover {
        background-color: #45a049;
    }
    QPushButton:pressed {
        background-color: #3e8e41;
    }
""")

# Global application stylesheet
app.setStyleSheet("""
    QMainWindow {
        background-color: #f0f0f0;
    }
    QLabel {
        font-family: Arial;
        font-size: 14px;
        color: #333;
    }
    QLineEdit {
        border: 2px solid #ddd;
        border-radius: 4px;
        padding: 8px;
        font-size: 14px;
    }
    QLineEdit:focus {
        border-color: #4CAF50;
    }
""")

Themes

class ThemeManager:
    @staticmethod
    def apply_dark_theme(app):
        dark_stylesheet = """
        QMainWindow {
            background-color: #2b2b2b;
            color: #ffffff;
        }
        QWidget {
            background-color: #2b2b2b;
            color: #ffffff;
        }
        QPushButton {
            background-color: #404040;
            border: 1px solid #555555;
            padding: 8px;
            border-radius: 4px;
        }
        QPushButton:hover {
            background-color: #505050;
        }
        QLineEdit {
            background-color: #404040;
            border: 1px solid #555555;
            padding: 5px;
            border-radius: 3px;
        }
        """
        app.setStyleSheet(dark_stylesheet)
    
    @staticmethod
    def apply_light_theme(app):
        app.setStyleSheet("")  # Revert to the default theme

Working with Qt Designer

Creating an Interface in Designer

  1. Launch Qt Designer via the designer command (for PyQt) or through Qt Creator.
  2. Create a new widget or main window.
  3. Drag the required components from the widget palette.
  4. Configure properties and layout.
  5. Save the file with a .ui extension.

Converting .ui Files to Python

# For PyQt5
pyuic5 interface.ui -o interface.py

# For PyQt6
pyuic6 interface.ui -o interface.py

# For PySide6
pyside6-uic interface.ui -o interface.py

Using .ui Files in Code

from PyQt5 import uic
from PyQt5.QtWidgets import QApplication, QMainWindow

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        # Load the UI from the .ui file
        uic.loadUi('interface.ui', self)
        
        # Connect signals
        self.pushButton.clicked.connect(self.on_button_clicked)
    
    def on_button_clicked(self):
        print("Button pressed!")

Working with QML

QML Basics

QML (Qt Modeling Language) is a declarative language for building modern user interfaces with animation and effect support.

Simple QML File (main.qml)

import QtQuick 2.15
import QtQuick.Controls 2.15

ApplicationWindow {
    visible: true
    width: 400
    height: 300
    title: "QML Application"
    
    Rectangle {
        anchors.fill: parent
        color: "#f0f0f0"
        
        Column {
            anchors.centerIn: parent
            spacing: 20
            
            Text {
                text: "Hello from QML!"
                font.pixelSize: 24
                color: "#333"
            }
            
            Button {
                text: "Press Me"
                onClicked: console.log("Button pressed!")
            }
            
            Rectangle {
                width: 200
                height: 50
                color: "lightblue"
                border.color: "blue"
                radius: 10
                
                Text {
                    anchors.centerIn: parent
                    text: "Nice block"
                }
            }
        }
    }
}

Integrating QML with Python

from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtCore import QObject, Signal, Slot

class Backend(QObject):
    # Signal to send data to QML
    dataChanged = Signal(str)
    
    @Slot(str)
    def process_data(self, data):
        """Slot for processing data from QML"""
        result = f"Processed: {data}"
        self.dataChanged.emit(result)
        return result

app = QGuiApplication([])
engine = QQmlApplicationEngine()

# Register the Python object in QML
backend = Backend()
engine.rootContext().setContextProperty("backend", backend)

# Load the QML file
engine.load("main.qml")

app.exec()

Event Handling and Advanced Techniques

Overriding Events

from PyQt5.QtCore import Qt
from PyQt5.QtGui import QKeyEvent, QCloseEvent

class CustomWidget(QWidget):
    def keyPressEvent(self, event: QKeyEvent):
        """Handle key presses"""
        if event.key() == Qt.Key_Escape:
            self.close()
        elif event.key() == Qt.Key_F11:
            if self.isFullScreen():
                self.showNormal()
            else:
                self.showFullScreen()
        else:
            super().keyPressEvent(event)
    
    def closeEvent(self, event: QCloseEvent):
        """Handle window close"""
        reply = QMessageBox.question(self, 'Confirmation',
                                   'Are you sure you want to exit?',
                                   QMessageBox.Yes | QMessageBox.No,
                                   QMessageBox.No)
        
        if reply == QMessageBox.Yes:
            event.accept()
        else:
            event.ignore()
    
    def mousePressEvent(self, event):
        """Handle mouse clicks"""
        if event.button() == Qt.LeftButton:
            print(f"Left click at position: {event.pos()}")
        elif event.button() == Qt.RightButton:
            print(f"Right click at position: {event.pos()}")

Working with Threads

from PyQt5.QtCore import QThread, pyqtSignal
import time

class WorkerThread(QThread):
    progress = pyqtSignal(int)
    finished = pyqtSignal(str)
    
    def run(self):
        """Run a long‑running task in a separate thread"""
        for i in range(101):
            time.sleep(0.1)  # Simulate work
            self.progress.emit(i)
        
        self.finished.emit("Task completed!")

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.worker = WorkerThread()
        self.worker.progress.connect(self.update_progress)
        self.worker.finished.connect(self.task_finished)
        
        self.progress_bar = QProgressBar()
        self.setCentralWidget(self.progress_bar)
        
        # Start the thread
        self.worker.start()
    
    def update_progress(self, value):
        self.progress_bar.setValue(value)
    
    def task_finished(self, message):
        QMessageBox.information(self, "Done", message)

Database Interaction

Connecting to SQLite

from PyQt5.QtSql import QSqlDatabase, QSqlQuery, QSqlTableModel
from PyQt5.QtWidgets import QTableView

class DatabaseManager:
    def __init__(self):
        self.db = QSqlDatabase.addDatabase('QSQLITE')
        self.db.setDatabaseName('app_database.db')
        
        if not self.db.open():
            print("Failed to connect to the database")
    
    def create_tables(self):
        query = QSqlQuery()
        query.exec_(\"\"\"
            CREATE TABLE IF NOT EXISTS users (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                name TEXT NOT NULL,
                email TEXT UNIQUE NOT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
        \"\"\")
    
    def add_user(self, name, email):
        query = QSqlQuery()
        query.prepare("INSERT INTO users (name, email) VALUES (?, ?)")
        query.addBindValue(name)
        query.addBindValue(email)
        return query.exec_()

class DatabaseView(QWidget):
    def __init__(self):
        super().__init__()
        self.db_manager = DatabaseManager()
        self.db_manager.create_tables()
        
        layout = QVBoxLayout()
        
        # Model for displaying data
        self.model = QSqlTableModel()
        self.model.setTable('users')
        self.model.select()
        
        # Table view
        self.table_view = QTableView()
        self.table_view.setModel(self.model)
        
        layout.addWidget(self.table_view)
        self.setLayout(layout)

Packaging the Application into an Executable

Preparation for Packaging

# main.py
import sys
import os
from PyQt5.QtWidgets import QApplication
from PyQt5.QtGui import QIcon

def resource_path(relative_path):
    """Get absolute path to resource, works for PyInstaller"""
    try:
        # PyInstaller creates a temporary folder and stores path in _MEIPASS
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.abspath(".")
    
    return os.path.join(base_path, relative_path)

class MainApp(QApplication):
    def __init__(self, argv):
        super().__init__(argv)
        
        # Set application icon
        self.setWindowIcon(QIcon(resource_path('icons/app_icon.ico')))
        
        # Create main window
        self.main_window = MainWindow()
        self.main_window.show()

if __name__ == '__main__':
    app = MainApp(sys.argv)
    sys.exit(app.exec_())

Building with PyInstaller

# Simple build
pyinstaller --onefile main.py

# Build with icon and additional data files
pyinstaller --onefile --windowed --icon=app_icon.ico --add-data "icons;icons" --add-data "data;data" main.py

# Create a spec file for complex projects
pyinstaller --onefile --windowed main.py
# Then edit main.spec and rebuild:
pyinstaller main.spec

Example spec File

# main.spec
a = Analysis(['main.py'],
             pathex=[],
             binaries=[],
             datas=[('icons', 'icons'), ('data', 'data')],
             hiddenimports=[],
             hookspath=[],
             hooksconfig={},
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=None,
             noarchive=False)

pyz = PYZ(a.pure, a.zipped_data, cipher=None)

exe = EXE(pyz,
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,
          [],
          name='MyApp',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          upx_exclude=[],
          runtime_tmpdir=None,
          console=False,
          disable_windowed_traceback=False,
          icon='app_icon.ico')

Detailed Comparison of PyQt and PySide

Parameter PyQt5 / PyQt6 PySide2 / PySide6
License GPL v3 / Commercial LGPL v3
Developer Riverbank Computing Qt Company (official)
Stability Very stable Stable
Documentation Good Excellent (official)
Performance High High
Signals & Slots pyqtSignal, pyqtSlot Signal, Slot
Additional Tooling pyqt5-tools Included in the core package
Python 2 Support PyQt5 – yes PySide2 – limited
Commercial Use Requires a license Free under LGPL compliance
Package Size Smaller Larger
Updates Regular Synchronized with Qt releases

Practical Projects

Task Manager

class TaskManager(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Task Manager")
        self.setGeometry(100, 100, 800, 600)
        
        # Central widget
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        # Layout
        layout = QVBoxLayout()
        
        # New task input
        input_layout = QHBoxLayout()
        self.task_input = QLineEdit()
        self.task_input.setPlaceholderText("Enter a new task...")
        self.add_button = QPushButton("Add")
        self.add_button.clicked.connect(self.add_task)
        
        input_layout.addWidget(self.task_input)
        input_layout.addWidget(self.add_button)
        
        # Task list
        self.task_list = QListWidget()
        self.task_list.itemDoubleClicked.connect(self.toggle_task)
        
        # Control buttons
        button_layout = QHBoxLayout()
        self.delete_button = QPushButton("Delete")
        self.delete_button.clicked.connect(self.delete_task)
        self.clear_button = QPushButton("Clear All")
        self.clear_button.clicked.connect(self.clear_all)
        
        button_layout.addWidget(self.delete_button)
        button_layout.addWidget(self.clear_button)
        
        # Assemble UI
        layout.addLayout(input_layout)
        layout.addWidget(self.task_list)
        layout.addLayout(button_layout)
        
        central_widget.setLayout(layout)
    
    def add_task(self):
        task_text = self.task_input.text().strip()
        if task_text:
            self.task_list.addItem(task_text)
            self.task_input.clear()
    
    def delete_task(self):
        current_row = self.task_list.currentRow()
        if current_row >= 0:
            self.task_list.takeItem(current_row)
    
    def clear_all(self):
        self.task_list.clear()
    
    def toggle_task(self, item):
        font = item.font()
        font.setStrikeOut(not font.strikeOut())
        item.setFont(font)

Calculator with History

class Calculator(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Smart Calculator")
        self.setFixedSize(400, 600)
        
        # History list
        self.history = []
        
        self.init_ui()
    
    def init_ui(self):
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        layout = QVBoxLayout()
        
        # Display
        self.display = QLineEdit()
        self.display.setReadOnly(True)
        self.display.setAlignment(Qt.AlignRight)
        self.display.setStyleSheet("font-size: 20px; padding: 10px;")
        
        # History view
        self.history_list = QListWidget()
        self.history_list.setMaximumHeight(150)
        self.history_list.itemClicked.connect(self.load_from_history)
        
        # Buttons grid
        buttons_layout = QGridLayout()
        
        buttons = [
            ('C', 0, 0), ('±', 0, 1), ('%', 0, 2), ('÷', 0, 3),
            ('7', 1, 0), ('8', 1, 1), ('9', 1, 2), ('×', 1, 3),
            ('4', 2, 0), ('5', 2, 1), ('6', 2, 2), ('-', 2, 3),
            ('1', 3, 0), ('2', 3, 1), ('3', 3, 2), ('+', 3, 3),
            ('0', 4, 0, 1, 2), ('.', 4, 2), ('=', 4, 3)
        ]
        
        for btn in buttons:
            text = btn[0]
            row = btn[1]
            col = btn[2]
            row_span = btn[3] if len(btn) > 3 else 1
            col_span = btn[4] if len(btn) > 4 else 1
            
            button = QPushButton(text)
            button.clicked.connect(lambda checked, t=text: self.button_clicked(t))
            button.setMinimumHeight(50)
            buttons_layout.addWidget(button, row, col, row_span, col_span)
        
        # Build UI
        layout.addWidget(QLabel("History:"))
        layout.addWidget(self.history_list)
        layout.addWidget(self.display)
        layout.addLayout(buttons_layout)
        
        central_widget.setLayout(layout)
        
        self.current_input = ""
        self.display.setText("0")
    
    def button_clicked(self, text):
        if text.isdigit() or text == '.':
            if self.current_input == "0":
                self.current_input = text
            else:
                self.current_input += text
        elif text in ['+', '-', '×', '÷']:
            self.current_input += f" {text} "
        elif text == '=':
            try:
                # Replace symbols for Python evaluation
                expression = self.current_input.replace('×', '*').replace('÷', '/')
                result = eval(expression)
                
                # Add to history
                history_item = f"{self.current_input} = {result}"
                self.history.append(history_item)
                self.history_list.addItem(history_item)
                
                self.current_input = str(result)
            except:
                self.current_input = "Error"
        elif text == 'C':
            self.current_input = "0"
        
        self.display.setText(self.current_input)
    
    def load_from_history(self, item):
        # Load result from history
        text = item.text()
        result = text.split(' = ')[-1]
        self.current_input = result
        self.display.setText(result)

Frequently Asked Questions

Which version should I choose: PyQt or PySide?

For commercial projects PySide6 is recommended because of its more permissive LGPL license. PyQt requires purchasing a commercial license or releasing the source under GPL.

How do I upgrade from PyQt5 to PyQt6?

Key changes:

  • exec_() has been replaced by exec()
  • Some modules have been moved (e.g., QtWebKit removed)
  • Adjustments in the event system

Can PyQt/PySide be used for mobile applications?

Yes, but with limitations. Qt supports Android and iOS, yet it requires special configuration and may not be optimal for native mobile development.

How can I optimise the performance of Qt applications?

  • Use data models instead of adding items one by one
  • Minimize the number of UI updates
  • Run long‑running tasks in separate threads
  • Employ lazy loading for large datasets

How do I create a system tray icon for my application?

from PyQt5.QtWidgets import QSystemTrayIcon, QMenu
from PyQt5.QtGui import QIcon

class SystemTrayApp:
    def __init__(self):
        self.tray_icon = QSystemTrayIcon()
        self.tray_icon.setIcon(QIcon("icon.png"))
        
        # Context menu
        menu = QMenu()
        menu.addAction("Show", self.show_window)
        menu.addAction("Exit", self.quit_app)
        
        self.tray_icon.setContextMenu(menu)
        self.tray_icon.show()

Conclusion

PyQt and PySide are powerful, mature tools for building professional graphical interfaces in Python. They provide full access to the Qt framework, including modern QML‑based UIs, database integration, networking capabilities, and cross‑platform compatibility.

The choice between PyQt and PySide primarily depends on the licensing requirements of your project. PySide6 with an LGPL license is generally preferred for commercial applications, whereas PyQt may require a commercial license.

Both libraries are actively developed and supported, boast extensive documentation, and have large developer communities. They are suitable for simple utilities as well as complex enterprise‑level applications with rich functionality.

News