вторник, 14 декабря 2021 г.

EPUB наше все

EPUB - это формат оформления электронных книг. Если вы хотите плотно поработать с уже готовой книгой или создать свою, то берите программу Sigil. Если вы создаете библиотеку на компьютере для себя, то обязательно возьмите Calibre.
Формат EPUB призван построить книгу как множество Интернет страниц в одном файле. Это  не так просто. Предполагается, что в страницы кроме текста могут быть вставлены графика, видео и голос. Для обеспечения движения есть javascript.

Файл книги в формате EPUB упакован в виде zip файла. Внутри есть дерево папок: Text для страниц xhtml; Styles для файлов стилей типа css; Images для файлов картинок - jpeg. png; Font для файлов фонтов, которые вы хотите использовать в книге; Audio и Vidio для фото и видиороликов; Misc в эту папку попадают программы на javascript.

Что бы удобнее было работать со страницами до загрузки в файл EPUB, используйте программу PageEDIT. Она находится там же, где и программа Sigil. Обе программы freeware. 

 

пятница, 10 декабря 2021 г.

PyQt6 против PySide6

 PyQt6 против PySide6

Есть новая версия Qt (версия 6), а вместе с ней и новые версии PyQt и PySide, которые теперь называются PyQt6 и PySide6 соответственно. При подготовке к выпуску Qt6 моих книг PyQt5 и PySide2 я просматривал последние версии библиотек, чтобы определить различия между ними и найти решения для написания переносимого кода.

В этом кратком руководстве я расскажу, почему существуют две библиотеки, нужно ли вам заботиться (спойлер: нет), каковы различия и как их обойти. К концу вам должно быть удобно повторно использовать примеры кода из руководств PyQt6 и PySide6 для создания своих приложений, независимо от того, какой пакет вы используете.

Истоки

Почему две библиотеки?

PyQt разработан Филом Томпсоном из Riverbank Computing Ltd. и существует очень долгое время - поддерживая версии Qt, начиная с 2.x. В 2009 году Nokia, которой в то время принадлежал инструментарий Qt, захотела сделать привязки Python для Qt доступными в рамках более разрешительной лицензии LGPL.

Он называется PySide, потому что «side» по-фински означает «связующее».

Эти два интерфейса были в основном эквивалентными, но со временем разработка PySide отставала от PyQt. Это было особенно заметно после выпуска Qt 5 - версия PyQt (PyQt5) для Qt5 была доступна с середины 2016 года, а первая стабильная версия PySide была выпущена двумя годами позже. Однако проект Qt недавно принял PySide в качестве официального выпуска Qt для Python, что должно гарантировать его жизнеспособность в будущем. Когда был выпущен Qt6, вскоре после этого были доступны обе привязки Python.


PyQt6PySide6
First stable releaseJan 2021Dec 2020
Developed byRiverbank Computing Ltd.Qt
LicenseGPL or commercialLGPL
PlatformsPython 3Python 3

Что вам следует использовать? Ну, честно говоря, это не имеет значения.

Оба пакета содержат одну и ту же библиотеку - Qt6 - и поэтому имеют 99,9% идентичных API (некоторые различия см. Ниже). Все, что вы узнаете с помощью одной библиотеки, будет легко применить к проекту с использованием другой. Кроме того, независимо от того, какой из них вы выберете для использования, стоит ознакомиться с другим, чтобы вы могли наилучшим образом использовать все доступные онлайн-ресурсы - например, используя учебные пособия PyQt6 для создания приложений PySide6, и наоборот.

В этом кратком обзоре я рассмотрю несколько заметных различий между двумя пакетами и объясню, как написать код, который без проблем работает с обоими. Прочитав это, вы сможете взять любой пример PyQt6 в Интернете и преобразовать его для работы с PySide6.

Лицензирование

Основное заметное различие между двумя версиями - это лицензирование: PyQt6 доступен по лицензии GPL или коммерческой лицензии, а PySide6 - по лицензии LGPL.

Если вы планируете выпустить свое программное обеспечение под лицензией GPL или разрабатываете программное обеспечение, которое не будет распространяться, требование GPL для PyQt6 вряд ли станет проблемой. Однако, если вы хотите распространять свое программное обеспечение, но не делитесь своим исходным кодом, вам необходимо приобрести коммерческую лицензию на PyQt6 у Riverbank или использовать PySide6.

Сам Qt доступен по коммерческой лицензии Qt, лицензиям GPL 2.0, GPL 3.0 и LGPL 3.0.


Пространства имен и перечисления

Одним из основных изменений, внесенных в PyQt6, является необходимость использования полностью определенных имен для перечислений и флагов. Раньше как в PyQt5, так и в PySide2 вы могли использовать ярлыки, например Qt.DecorationRole, Qt.AlignLeft. В PyQt6 теперь это Qt.ItemDataRole.DisplayRole и Qt.Alignment.AlignLeft соответственно. Это изменение затрагивает все перечисления и группы флагов в Qt. В PySide6 по-прежнему поддерживаются как длинные, так и короткие имена.

Ситуация несколько осложняется тем фактом, что PyQt6 и PySide6 используют несколько разные соглашения об именах для флагов. В PySide6 (и v5) флаги сгруппированы под объектами флагов с суффиксом «Flag», например Qt.AlignmentFlag - флаг выравнивания по левому краю - Qt.AlignmentFlag.AlignLeft. Та же группа флагов в PyQt6 называется просто «Qt.Alignment». Это означает, что вы не можете просто выбрать длинную или краткую форму и сохранить совместимость между PyQt6 и PySide6.

UI файлы

Еще одно важное различие между двумя библиотеками заключается в их обработке загрузки файлов .ui, экспортированных из Qt Creator / Designer. PyQt6 предоставляет подмодуль uic, который можно использовать для непосредственной загрузки файлов пользовательского интерфейса для создания объекта. Это выглядит довольно Pythonic (если не обращать внимания на camelCase).

PYTHON
import sys
from PyQt6 import QtWidgets, uic

app = QtWidgets.QApplication(sys.argv)

window = uic.loadUi("mainwindow.ui")
window.show()
app.exec()

Эквивалент с PySide6 на одну строку длиннее, так как вам нужно сначала создать объект QUILoader. К сожалению, API этих двух интерфейсов тоже различается (.load и .loadUI).


PYTHON
import sys
from PySide6 import QtCore, QtGui, QtWidgets
from PySide6.QtUiTools import QUiLoader

loader = QUiLoader()

app = QtWidgets.QApplication(sys.argv)
window = loader.load("mainwindow.ui", None)
window.show()
app.exec_()

Чтобы загрузить пользовательский интерфейс в существующий объект в PyQt6, например, в вашем QMainWindow .__ init__, вы можете вызвать uic.loadUI, передав self (существующий виджет) в качестве второго параметра.

PYTHON
import sys
from PyQt6 import QtCore, QtGui, QtWidgets
from PyQt6 import uic


class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        uic.loadUi("mainwindow.ui", self)


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

Загрузчик PySide6 не поддерживает это - второй параметр .load - это родительский виджет того виджета, который вы создаете. Это предотвращает добавление пользовательского кода в блок __init__ виджета, но вы можете обойти это с помощью отдельной функции.

PYTHON
import sys
from PySide6 import QtWidgets
from PySide6.QtUiTools import QUiLoader

loader = QUiLoader()

def mainwindow_setup(w):
    w.setWindowTitle("MainWindow Title")

app = QtWidgets.QApplication(sys.argv)

window = loader.load("mainwindow.ui", None)
mainwindow_setup(window)
window.show()
app.exec()

Преобразование файлов UI в Python

Обе библиотеки предоставляют идентичные сценарии для создания импортируемых модулей Python из файлов .ui Qt Designer. Для PyQt6 скрипт называется pyuic6 -

BASH
pyuic6 mainwindow.ui -o MainWindow.py

Затем вы можете импортировать объект UI_MainWindow, подкласс, используя множественное наследование из базового класса, который вы используете (например, QMainWIndow), а затем вызвать self.setupUI (self) для настройки пользовательского интерфейса.

PYTHON
import sys
from PyQt6 import QtWidgets
from MainWindow import Ui_MainWindow

class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setupUi(self)


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

Для PySide6 он называется pyside6-uic -

BASH
pyside6-uic mainwindow.ui -o MainWindow.py

Последующая настройка идентична.

PYTHON
import sys
from PySide6 import QtWidgets
from MainWindow import Ui_MainWindow

class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setupUi(self)


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

Компилятор пользовательского интерфейса PyQt имеет флаг -x, который можно использовать для создания исполняемой демонстрации из файла пользовательского интерфейса. Это недоступно в версии PySide.

exec() или exec_()

Метод .exec () используется в Qt для запуска цикла обработки событий вашего QApplication или диалоговых окон. В Python 2.7 exec было ключевым словом, что означало, что его нельзя было использовать для имен переменных, функций или методов. Решение, используемое как в PyQt4, так и в PySide, заключалось в переименовании использования .exec в .exec_ (), чтобы избежать этого конфликта.

Python 3 удалил ключевое слово exec, освободив имя для использования. В результате из PyQt6 вызовы .exec () именуются так же, как в Qt. Однако PySide6 по-прежнему использует .exec_ ().

 Slots и Signals

Для определения пользовательских слотов и сигналов используется немного другой синтаксис между двумя библиотеками. PySide6 предоставляет этот интерфейс под именами Signal и Slot, тогда как PyQt6 предоставляет их как pyqtSignal и pyqtSlot соответственно. Поведение у них обоих идентично для определения и слотов, и сигналов.

Следующие примеры PyQt6 и PySide6 идентичны -

PYTHON
my_custom_signal = pyqtSignal()  # PyQt6
my_custom_signal = Signal()  # PySide6

my_other_signal = pyqtSignal(int)  # PyQt6
my_other_signal = Signal(int)  # PySide6

Или для слота -

PYTHON
@pyqtslot
def my_custom_slot():
    pass

@Slot
def my_custom_slot():
    pass

Если вы хотите обеспечить согласованность между PyQt6 и PySide6, вы можете использовать следующий шаблон импорта для PyQt6, чтобы использовать там стиль Signal и @Slot.

PYTHON
from PyQt6.QtCore import pyqtSignal as Signal, pyqtSlot as Slot

Конечно, вы могли бы сделать обратное из PySide6.QtCore import Signal как pyqtSignal, Slot как pyqtSlot, хотя это немного сбивает с толку.

QMouseEvent

В PyQt6 объекты QMouseEvent больше не имеют сокращенных методов свойств .pos (), .x () или .y () для доступа к позиции события. Вы должны использовать свойство .position (), чтобы получить объект QPoint и получить доступ к его методам .x () или .y (). Метод .position () также доступен в PySide6.

Свойства которые есть в PySide6 но нет в PyQt6

Начиная с Qt6 PySide поддерживает два флага Python __feature__, чтобы помочь сделать код более Pythonic с именами переменных snake_case и возможностью назначать свойства и обращаться к ним напрямую, вместо использования функций getter/setter. В приведенном ниже примере показано влияние этих изменений на код:

PYTHON
table = QTableWidget()
table.setColumnCount(2)

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

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

Тот же код, но с включенными snake_case и true_property.


PYTHON
from __feature__ import snake_case, true_property

table = QTableWidget()
table.column_count = 2

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

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

Эти флаги свойств являются хорошим улучшением для читабельности кода, однако, поскольку они не поддерживаются в PyQt6, это затрудняет создание переносимого кода.

Поддержка библиотек

Вам не нужно беспокоиться об этом, если вы пишете автономное приложение, просто используйте любой API, который вам больше нравится.

Если вы пишете библиотеку, виджет или другой инструмент, и хотите что бы они были совместимыми как с PyQt6, так и с PySide6, вы можете легко сделать это, добавив оба набора импорта.

PYTHON
import sys

if 'PyQt6' in sys.modules:
    # PyQt6
    from PyQt6 import QtGui, QtWidgets, QtCore
    from PyQt6.QtCore import pyqtSignal as Signal, pyqtSlot as Slot

else:
    # PySide6
    from PySide6 import QtGui, QtWidgets, QtCore
    from PySide6.QtCore import Signal, Slot
Это подход, используемый в нашей библиотеке пользовательских виджетов, где мы поддерживаем PyQt6 и PySide6 с помощью одного импорта библиотеки. Единственное предостережение заключается в том, что вы должны убедиться, что PyQt6 импортирован раньше (как в строке выше или ранее) при импорте этой библиотеки, чтобы убедиться, что он находится в sys.modules.

Чтобы учесть отсутствие сокращенного перечисления и флагов в PyQt6, вы можете сгенерировать их самостоятельно. Например, следующий код скопирует ссылки для каждого из элементов объектов перечисления до их родительского объекта, что сделает их доступными, как в PyQt5, PySide2 и PySide6. Код нужно будет запускать только под PyQt6.
PYTHON
enums = [
    (QtCore.Qt, 'Alignment'),
    (QtCore.Qt, 'ApplicationAttribute'),
    (QtCore.Qt, 'CheckState'),
    (QtCore.Qt, 'CursorShape'),
    (QtWidgets.QSizePolicy, 'Policy'),
]

# Look up using the long name (e.g. QtCore.Qt.CheckState.Checked, used
# in PyQt6) and store under the short name (e.g. QtCore.Checked, used
# in PyQt5, PySide2 & accepted by PySide6).
for module, enum_name in enums:
    for entry in getattr(module, enum_name):
        setattr(module, entry.name, entry)

В качестве альтернативы вы можете определить пользовательскую функцию для обработки поиска в пространстве имен.

PYTHON
def _enum(obj, name):
    parent, child = name.split('.')
    result = getattr(obj, child, False)
    if result:  # Found using short name only.
        return result

    obj = getattr(obj, parent)  # Get parent, then child.
    return getattr(obj, child)

При передаче объекта и длинного имени, совместимого с PyQt6, эта функция вернет правильное перечисление или флаг как для PyQt6, так и для PySide6.

PYTHON
>>> _enum(PySide6.QtCore.Qt, 'Alignment.AlignLeft')
PySide6.QtCore.Qt.AlignmentFlag.AlignLeft
>>> _enum(PyQt6.QtCore.Qt, 'Alignment.AlignLeft')
<Alignment.AlignLeft: 1>

Последняя сложность - несоответствие в вызовах методов exec_ () и exec (). Вы можете обойти это, реализовав функцию для проверки наличия каждого метода и вызова того, который существует.

PYTHON
def _exec(obj):
    if hasattr(obj, 'exec'):
        return obj.exec()
    else:
        return obj.exec_()

Затем вы можете использовать эту функцию для выполнения таких объектов, как QApplication и QDialog, переносимым способом как на PyQt6, так и на PySide6.

PYTHON
app = QApplication(sys.argv)
_exec(app)
Если вы делаете это в нескольких файлах, это может стать немного громоздким. Хорошее решение - переместить логику импорта и настраиваемые методы прокладки в отдельный файл, например с именем qt.py в корне вашего проекта. Этот модуль импортирует модули Qt (QtCore, QtGui, QtWidgets и т. Д.) Из одной из двух библиотек, а затем вы импортируете оттуда в свое приложение.

Содержимое qt.py такое же, как мы использовали ранее -
PYTHON
import sys

if 'PyQt6' in sys.modules:
    # PyQt6
    from PyQt6 import QtGui, QtWidgets, QtCore
    from PyQt6.QtCore import pyqtSignal as Signal, pyqtSlot as Slot


else:
    # PySide6
    from PySide6 import QtGui, QtWidgets, QtCore
    from PySide6.QtCore import Signal, Slot


def _enum(obj, name):
    parent, child = name.split('.')
    result = getattr(obj, child, False)
    if result:  # Found using short name only.
        return result

    obj = getattr(obj, parent)  # Get parent, then child.
    return getattr(obj, child)


def _exec(obj):
    if hasattr(obj, 'exec'):
        return obj.exec()
    else:
        return obj.exec_()

Вы должны не забыть добавить любые другие используемые вами модули PyQt6 (браузер, мультимедиа и т. Д.) В обе ветви блока if. Затем вы можете импортировать Qt6 в свое собственное приложение следующим образом:

PYTHON
from .qt import QtGui, QtWidgets, QtCore, _enum, _exec

... и он будет без проблем работать с любой библиотекой.

Это действительно то что нужно

Больше нечего сказать - две библиотеки действительно настолько похожи. Однако, если вы наткнетесь на какие-либо другие примеры или функции PyQt6 / PySide6, которые вы не можете легко преобразовать, напишите мне.

 

X-Plane 11, 12 - любитель, Фото любитель со стажем

Постоянные читатели

Архив блога