PyQt/PySide – разработка десктопных приложений

онлайн тренажер по питону
Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение

PyQt и PySide — это мощные библиотеки для создания кроссплатформенных графических интерфейсов на Python с использованием фреймворка Qt. Они предоставляют доступ к богатому набору инструментов Qt: виджетам, системам событий, стилям, анимациям, работе с базами данных и даже QML для создания современных интерфейсов.

Главное отличие между PyQt и PySide заключается в лицензировании: PyQt требует GPL или коммерческую лицензию, а PySide (официальная реализация от Qt Company) распространяется под более либеральной LGPL, что делает его предпочтительным для коммерческих проектов.

В этом подробном руководстве мы рассмотрим все аспекты работы с этими библиотеками: от установки и создания простых интерфейсов до сложных проектов с базами данных, стилизацией и сборкой готовых приложений.

Установка и настройка

Установка PyQt5 и PyQt6

# PyQt5 (стабильная версия)
pip install pyqt5 pyqt5-tools

# PyQt6 (современная версия)
pip install pyqt6 pyqt6-tools

Установка PySide2 и PySide6

# PySide2 (для Qt 5)
pip install pyside2

# PySide6 (рекомендуемая версия)
pip install pyside6

Проверка установки

# Для PyQt5
from PyQt5.QtWidgets import QApplication
print("PyQt5 установлен успешно")

# Для PySide6
from PySide6.QtWidgets import QApplication
print("PySide6 установлен успешно")

Основы создания приложений

Простое окно с кнопкой

PyQt5 версия

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

class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Мое первое приложение PyQt5')
        self.setGeometry(100, 100, 300, 200)
        
        layout = QVBoxLayout()
        button = QPushButton('Нажми меня')
        button.clicked.connect(self.on_click)
        layout.addWidget(button)
        
        self.setLayout(layout)
    
    def on_click(self):
        print("Кнопка нажата!")

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

PySide6 версия

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

class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Мое первое приложение PySide6')
        self.setGeometry(100, 100, 300, 200)
        
        layout = QVBoxLayout()
        button = QPushButton('Нажми меня')
        button.clicked.connect(self.on_click)
        layout.addWidget(button)
        
        self.setLayout(layout)
    
    def on_click(self):
        print("Кнопка нажата!")

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

Архитектура Qt приложений

Основные компоненты

QApplication — это центральный объект любого Qt приложения. Он управляет циклом событий, обрабатывает системные события и координирует работу всех виджетов.

QWidget — базовый класс для всех элементов пользовательского интерфейса. Может содержать другие виджеты и служить контейнером.

QMainWindow — специализированный виджет для создания главного окна приложения с поддержкой меню, панелей инструментов, статусной строки и центрального виджета.

QDialog — класс для создания диалоговых окон, включая модальные и немодальные диалоги.

Жизненный цикл приложения

  1. Создание экземпляра QApplication
  2. Создание и настройка виджетов
  3. Отображение главного окна
  4. Запуск цикла событий через exec() или exec_()
  5. Завершение приложения

Система сигналов и слотов

Основные принципы

Сигналы и слоты — это механизм Qt для обработки событий и межобъектного взаимодействия. Сигнал генерируется при возникновении события, а слот — это функция, которая его обрабатывает.

Подключение сигналов

# Простое подключение
button.clicked.connect(self.on_button_clicked)

# Подключение с параметрами
button.clicked.connect(lambda: self.process_data("параметр"))

# Подключение к встроенному слоту
button.clicked.connect(self.close)

# Отключение сигнала
button.clicked.disconnect()

Создание пользовательских сигналов

from PyQt5.QtCore import pyqtSignal, QObject

class DataProcessor(QObject):
    # Определение пользовательского сигнала
    data_processed = pyqtSignal(str)
    progress_updated = pyqtSignal(int)
    
    def process_data(self):
        # Эмитирование сигнала с данными
        self.progress_updated.emit(50)
        self.data_processed.emit("Обработка завершена")

Виджеты и компоненты интерфейса

Основные виджеты ввода

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

class InputDemo(QWidget):
    def __init__(self):
        super().__init__()
        layout = QVBoxLayout()
        
        # Текстовые поля
        self.line_edit = QLineEdit("Однострочный ввод")
        self.text_edit = QTextEdit("Многострочный ввод")
        
        # Числовые поля
        self.spin_box = QSpinBox()
        self.spin_box.setRange(0, 100)
        
        # Выбор даты и времени
        self.date_edit = QDateEdit()
        self.time_edit = QTimeEdit()
        
        # Чекбоксы и радиокнопки
        self.checkbox = QCheckBox("Включить опцию")
        self.radio1 = QRadioButton("Вариант 1")
        self.radio2 = QRadioButton("Вариант 2")
        
        # Выпадающий список
        self.combo_box = QComboBox()
        self.combo_box.addItems(["Элемент 1", "Элемент 2", "Элемент 3"])
        
        # Ползунки
        self.slider = QSlider(Qt.Horizontal)
        self.slider.setRange(0, 100)
        
        # Добавление всех виджетов в 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)

Отображение данных

class DisplayDemo(QWidget):
    def __init__(self):
        super().__init__()
        layout = QVBoxLayout()
        
        # Метки
        self.label = QLabel("Простая метка")
        self.rich_label = QLabel("<b>Жирный</b> <i>курсив</i> <u>подчеркнутый</u>")
        
        # Прогресс-бар
        self.progress = QProgressBar()
        self.progress.setValue(75)
        
        # Таблица
        self.table = QTableWidget(3, 2)
        self.table.setHorizontalHeaderLabels(["Колонка 1", "Колонка 2"])
        
        # Список
        self.list_widget = QListWidget()
        self.list_widget.addItems(["Элемент 1", "Элемент 2", "Элемент 3"])
        
        # Древовидный список
        self.tree = QTreeWidget()
        self.tree.setHeaderLabels(["Название", "Значение"])
        
        for widget in [self.label, self.rich_label, self.progress, 
                      self.table, self.list_widget, self.tree]:
            layout.addWidget(widget)
        
        self.setLayout(layout)

Полная таблица методов и функций

Компонент Метод/Свойство Описание Пример использования
QWidget setWindowTitle() Установка заголовка окна widget.setWindowTitle("Мое окно")
  setGeometry() Размер и позиция окна widget.setGeometry(100, 100, 800, 600)
  show() Отображение виджета widget.show()
  hide() Скрытие виджета widget.hide()
  close() Закрытие виджета widget.close()
  setEnabled() Включение/отключение widget.setEnabled(False)
  setStyleSheet() Применение CSS стилей widget.setStyleSheet("color: red;")
QPushButton clicked Сигнал нажатия button.clicked.connect(func)
  setText() Установка текста button.setText("Новый текст")
  setIcon() Установка иконки button.setIcon(QIcon("icon.png"))
  setCheckable() Кнопка-переключатель button.setCheckable(True)
QLineEdit textChanged Сигнал изменения текста edit.textChanged.connect(func)
  text() Получение текста text = edit.text()
  setText() Установка текста edit.setText("Новый текст")
  setPlaceholderText() Текст-подсказка edit.setPlaceholderText("Введите...")
  setValidator() Установка валидатора edit.setValidator(QIntValidator())
QLabel setText() Установка текста label.setText("Текст")
  setPixmap() Установка изображения label.setPixmap(QPixmap("image.png"))
  setAlignment() Выравнивание label.setAlignment(Qt.AlignCenter)
QComboBox addItem() Добавление элемента combo.addItem("Элемент")
  addItems() Добавление списка combo.addItems(["1", "2", "3"])
  currentText() Текущий выбранный текст text = combo.currentText()
  currentIndexChanged Сигнал смены индекса combo.currentIndexChanged.connect(func)
QTableWidget setRowCount() Количество строк table.setRowCount(10)
  setColumnCount() Количество столбцов table.setColumnCount(5)
  setItem() Установка элемента table.setItem(0, 0, QTableWidgetItem("Текст"))
  setHorizontalHeaderLabels() Заголовки столбцов table.setHorizontalHeaderLabels(["Кол1", "Кол2"])
QListWidget addItem() Добавление элемента list.addItem("Элемент")
  addItems() Добавление списка list.addItems(["1", "2", "3"])
  currentRow() Текущая строка row = list.currentRow()
  itemClicked Сигнал клика по элементу list.itemClicked.connect(func)
QApplication exec_() / exec() Запуск цикла событий app.exec_()
  quit() Завершение приложения app.quit()
  processEvents() Обработка событий app.processEvents()

Компоновка интерфейса (Layouts)

Основные типы компоновщиков

# Вертикальная компоновка
v_layout = QVBoxLayout()
v_layout.addWidget(widget1)
v_layout.addWidget(widget2)

# Горизонтальная компоновка
h_layout = QHBoxLayout()
h_layout.addWidget(widget1)
h_layout.addWidget(widget2)

# Сетка
grid_layout = QGridLayout()
grid_layout.addWidget(widget1, 0, 0)  # строка 0, столбец 0
grid_layout.addWidget(widget2, 0, 1)  # строка 0, столбец 1
grid_layout.addWidget(widget3, 1, 0, 1, 2)  # строка 1, столбцы 0-1

# Форма
form_layout = QFormLayout()
form_layout.addRow("Имя:", QLineEdit())
form_layout.addRow("Email:", QLineEdit())

Вложенные компоновки

class ComplexLayout(QWidget):
    def __init__(self):
        super().__init__()
        
        # Главная вертикальная компоновка
        main_layout = QVBoxLayout()
        
        # Верхняя горизонтальная панель
        top_layout = QHBoxLayout()
        top_layout.addWidget(QLabel("Заголовок"))
        top_layout.addWidget(QPushButton("Настройки"))
        
        # Средняя часть с сеткой
        grid_layout = QGridLayout()
        grid_layout.addWidget(QLabel("Поле 1:"), 0, 0)
        grid_layout.addWidget(QLineEdit(), 0, 1)
        grid_layout.addWidget(QLabel("Поле 2:"), 1, 0)
        grid_layout.addWidget(QLineEdit(), 1, 1)
        
        # Нижняя панель кнопок
        button_layout = QHBoxLayout()
        button_layout.addWidget(QPushButton("ОК"))
        button_layout.addWidget(QPushButton("Отмена"))
        
        # Сборка всех компоновок
        main_layout.addLayout(top_layout)
        main_layout.addLayout(grid_layout)
        main_layout.addLayout(button_layout)
        
        self.setLayout(main_layout)

Стилизация интерфейса

Qt Style Sheets (CSS-подобные стили)

# Стилизация отдельного виджета
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;
    }
""")

# Глобальные стили для всего приложения
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;
    }
""")

Темы оформления

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("")  # Возврат к стандартной теме

Работа с Qt Designer

Создание интерфейса в Designer

  1. Запустите Qt Designer через команду designer (для PyQt) или через Qt Creator
  2. Создайте новый виджет или главное окно
  3. Перетащите нужные компоненты из панели виджетов
  4. Настройте свойства и компоновку
  5. Сохраните файл с расширением .ui

Конвертация .ui файлов в Python

# Для PyQt5
pyuic5 interface.ui -o interface.py

# Для PyQt6
pyuic6 interface.ui -o interface.py

# Для PySide6
pyside6-uic interface.ui -o interface.py

Использование .ui файлов в коде

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

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        # Загрузка интерфейса из .ui файла
        uic.loadUi('interface.ui', self)
        
        # Подключение сигналов
        self.pushButton.clicked.connect(self.on_button_clicked)
    
    def on_button_clicked(self):
        print("Кнопка нажата!")

Работа с QML

Основы QML

QML (Qt Modeling Language) — это декларативный язык для создания современных пользовательских интерфейсов с поддержкой анимаций и эффектов.

Простой QML файл (main.qml)

import QtQuick 2.15
import QtQuick.Controls 2.15

ApplicationWindow {
    visible: true
    width: 400
    height: 300
    title: "QML приложение"
    
    Rectangle {
        anchors.fill: parent
        color: "#f0f0f0"
        
        Column {
            anchors.centerIn: parent
            spacing: 20
            
            Text {
                text: "Привет из QML!"
                font.pixelSize: 24
                color: "#333"
            }
            
            Button {
                text: "Нажми меня"
                onClicked: console.log("Кнопка нажата!")
            }
            
            Rectangle {
                width: 200
                height: 50
                color: "lightblue"
                border.color: "blue"
                radius: 10
                
                Text {
                    anchors.centerIn: parent
                    text: "Красивый блок"
                }
            }
        }
    }
}

Интеграция QML с Python

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

class Backend(QObject):
    # Сигнал для отправки данных в QML
    dataChanged = Signal(str)
    
    @Slot(str)
    def process_data(self, data):
        """Слот для обработки данных из QML"""
        result = f"Обработано: {data}"
        self.dataChanged.emit(result)
        return result

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

# Регистрация Python объекта в QML
backend = Backend()
engine.rootContext().setContextProperty("backend", backend)

# Загрузка QML файла
engine.load("main.qml")

app.exec()

Обработка событий и продвинутые техники

Переопределение событий

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

class CustomWidget(QWidget):
    def keyPressEvent(self, event: QKeyEvent):
        """Обработка нажатий клавиш"""
        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):
        """Обработка закрытия окна"""
        reply = QMessageBox.question(self, 'Подтверждение',
                                   'Вы уверены, что хотите выйти?',
                                   QMessageBox.Yes | QMessageBox.No,
                                   QMessageBox.No)
        
        if reply == QMessageBox.Yes:
            event.accept()
        else:
            event.ignore()
    
    def mousePressEvent(self, event):
        """Обработка кликов мыши"""
        if event.button() == Qt.LeftButton:
            print(f"Левый клик в позиции: {event.pos()}")
        elif event.button() == Qt.RightButton:
            print(f"Правый клик в позиции: {event.pos()}")

Работа с потоками (Threading)

from PyQt5.QtCore import QThread, pyqtSignal
import time

class WorkerThread(QThread):
    progress = pyqtSignal(int)
    finished = pyqtSignal(str)
    
    def run(self):
        """Выполнение долгой задачи в отдельном потоке"""
        for i in range(101):
            time.sleep(0.1)  # Имитация работы
            self.progress.emit(i)
        
        self.finished.emit("Задача выполнена!")

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)
        
        # Запуск потока
        self.worker.start()
    
    def update_progress(self, value):
        self.progress_bar.setValue(value)
    
    def task_finished(self, message):
        QMessageBox.information(self, "Готово", message)

Работа с базами данных

Подключение к 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("Ошибка подключения к базе данных")
    
    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()
        
        # Модель для отображения данных
        self.model = QSqlTableModel()
        self.model.setTable('users')
        self.model.select()
        
        # Представление таблицы
        self.table_view = QTableView()
        self.table_view.setModel(self.model)
        
        layout.addWidget(self.table_view)
        self.setLayout(layout)

Сборка приложения в исполняемый файл

Подготовка к сборке

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

def resource_path(relative_path):
    """Получить абсолютный путь к ресурсу"""
    try:
        # PyInstaller создает временную папку и сохраняет путь в _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)
        
        # Установка иконки приложения
        self.setWindowIcon(QIcon(resource_path('icons/app_icon.ico')))
        
        # Создание главного окна
        self.main_window = MainWindow()
        self.main_window.show()

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

Сборка с PyInstaller

# Простая сборка
pyinstaller --onefile main.py

# Сборка с иконкой и дополнительными файлами
pyinstaller --onefile --windowed --icon=app_icon.ico --add-data "icons;icons" --add-data "data;data" main.py

# Создание spec файла для сложных проектов
pyinstaller --onefile --windowed main.py
# Затем отредактировать main.spec и пересобрать:
pyinstaller main.spec

Пример spec файла

# 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')

Подробное сравнение PyQt и PySide

Параметр PyQt5/PyQt6 PySide2/PySide6
Лицензия GPL v3 / Коммерческая LGPL v3
Разработчик Riverbank Computing Qt Company (официально)
Стабильность Очень стабильная Стабильная
Документация Хорошая Отличная (официальная)
Производительность Высокая Высокая
Сигналы и слоты pyqtSignal, pyqtSlot Signal, Slot
Установка дополнительных инструментов pyqt5-tools Включены в основной пакет
Поддержка Python 2 PyQt5 - да PySide2 - ограниченная
Коммерческое использование Требует лицензии Бесплатно при соблюдении LGPL
Размер пакета Меньше Больше
Обновления Регулярные Синхронизированы с Qt

Практические проекты

Менеджер задач

class TaskManager(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Менеджер задач")
        self.setGeometry(100, 100, 800, 600)
        
        # Центральный виджет
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        # Компоновка
        layout = QVBoxLayout()
        
        # Поле ввода новой задачи
        input_layout = QHBoxLayout()
        self.task_input = QLineEdit()
        self.task_input.setPlaceholderText("Введите новую задачу...")
        self.add_button = QPushButton("Добавить")
        self.add_button.clicked.connect(self.add_task)
        
        input_layout.addWidget(self.task_input)
        input_layout.addWidget(self.add_button)
        
        # Список задач
        self.task_list = QListWidget()
        self.task_list.itemDoubleClicked.connect(self.toggle_task)
        
        # Кнопки управления
        button_layout = QHBoxLayout()
        self.delete_button = QPushButton("Удалить")
        self.delete_button.clicked.connect(self.delete_task)
        self.clear_button = QPushButton("Очистить все")
        self.clear_button.clicked.connect(self.clear_all)
        
        button_layout.addWidget(self.delete_button)
        button_layout.addWidget(self.clear_button)
        
        # Сборка интерфейса
        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)

Калькулятор с историей

class Calculator(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Умный калькулятор")
        self.setFixedSize(400, 600)
        
        # История вычислений
        self.history = []
        
        self.init_ui()
    
    def init_ui(self):
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        layout = QVBoxLayout()
        
        # Дисплей
        self.display = QLineEdit()
        self.display.setReadOnly(True)
        self.display.setAlignment(Qt.AlignRight)
        self.display.setStyleSheet("font-size: 20px; padding: 10px;")
        
        # История
        self.history_list = QListWidget()
        self.history_list.setMaximumHeight(150)
        self.history_list.itemClicked.connect(self.load_from_history)
        
        # Кнопки
        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 button_data in buttons:
            text = button_data[0]
            row = button_data[1]
            col = button_data[2]
            row_span = button_data[3] if len(button_data) > 3 else 1
            col_span = button_data[4] if len(button_data) > 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)
        
        # Сборка интерфейса
        layout.addWidget(QLabel("История:"))
        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:
                # Замена символов для Python
                expression = self.current_input.replace('×', '*').replace('÷', '/')
                result = eval(expression)
                
                # Добавление в историю
                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 = "Ошибка"
        elif text == 'C':
            self.current_input = "0"
        
        self.display.setText(self.current_input)
    
    def load_from_history(self, item):
        # Загрузка результата из истории
        text = item.text()
        result = text.split(' = ')[-1]
        self.current_input = result
        self.display.setText(result)

Часто задаваемые вопросы

Какую версию выбрать: PyQt или PySide?

Для коммерческих проектов рекомендуется PySide6 из-за более либеральной лицензии LGPL. PyQt требует покупки коммерческой лицензии или открытия исходного кода под GPL.

Как обновиться с PyQt5 на PyQt6?

Основные изменения:

  • exec_() заменено на exec()
  • Некоторые модули перенесены (например, QtWebKit удален)
  • Изменения в системе событий

Можно ли использовать PyQt/PySide для мобильных приложений?

Да, но ограниченно. Qt поддерживает Android и iOS, но требует специальной настройки и может быть не оптимальным для мобильной разработки.

Как оптимизировать производительность Qt приложений?

  • Используйте модели данных вместо добавления элементов по одному
  • Минимизируйте количество обновлений интерфейса
  • Используйте потоки для долгих операций
  • Применяйте ленивую загрузку для больших данных

Как создать системный трей для приложения?

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"))
        
        # Контекстное меню
        menu = QMenu()
        menu.addAction("Показать", self.show_window)
        menu.addAction("Выход", self.quit_app)
        
        self.tray_icon.setContextMenu(menu)
        self.tray_icon.show()

Заключение

PyQt и PySide представляют собой мощные и зрелые инструменты для создания профессиональных графических интерфейсов на Python. Они обеспечивают полный доступ к возможностям фреймворка Qt, включая современные интерфейсы с QML, работу с базами данных, сетевые возможности и кроссплатформенную совместимость.

Выбор между PyQt и PySide в первую очередь зависит от лицензионных требований вашего проекта. PySide6 с лицензией LGPL является более предпочтительным для коммерческих проектов, в то время как PyQt может потребовать приобретения коммерческой лицензии.

Обе библиотеки активно развиваются и поддерживаются, имеют обширную документацию и большое сообщество разработчиков. Они подходят как для создания простых утилит, так и для разработки сложных enterprise-приложений с богатым функционалом.

Новости