вторник, 27 сентября 2022 г.

Про Glib в языке C от IBM


 

Про Glib в языке C от IBM

Оригинал: Manage C data using the GLib collections

Автор: Tom Copeland

 Published 27 июня 2005 г.

Перед началом работы

Об этом руководстве

В этом руководстве показано, как использовать коллекции GLib для эффективного и элегантного управления данными в ваших программах на C. Коллекции GLib являются результатом многолетнего усовершенствования и используются многочисленными программами с открытым исходным кодом. Эти коллекции предоставляют более сложные структуры данных/контейнеры (функции и переменные, необходимые для управления данными), которых не хватает в языке C.

Это руководство написано для программистов Linux или UNIX, чьи навыки и опыт находятся на уровне от начального до среднего.

Предварительные условия

Чтобы получить максимальную отдачу от этого руководства, вы должны быть знакомы с UNIX-подобной средой и знать, как использовать оболочку командной строки.

Вам также потребуются некоторые базовые инструменты программирования для компиляции примеров исходного кода, такие как компилятор, такой как GCC (см. раздел "Ресурсы" для загрузки GCC); все примеры кода в этом руководстве были скомпилированы с помощью GCC 3.4.2.

Вам также необходимо установить среду выполнения GLib и библиотеки разработки. Большинство современных дистрибутивов Linux поставляются с установленной средой выполнения GLib; например, установка Fedora Core 3 для «рабочей станции» поставляется с двумя пакетами RPM GLib: glib2-2.4.7-1 и glib2-devel-2.4.7-1.

Упорядочивание данных

Область действия GLib

Сначала давайте рассмотрим возможности GLib.

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

GLib также определяет ряд структур данных (и связанных с ними операций), в том числе:

  • Фрагменты памяти
  • Двусвязные списки
  • Односвязные списки
  • Хеш-таблицы
  • Строки (которые могут динамически увеличиваться)
  • Фрагменты строк (группы строк)
  • Массивы (размер которых может увеличиваться по мере добавления элементов)
  • Сбалансированные бинарные деревья
  • N-арные деревья
  • Кварки (двусторонняя ассоциация строки и уникального целочисленного идентификатора)
  • Списки данных с ключами (списки элементов данных, доступных по строковому или целочисленному идентификатору)
  • Отношения и кортежи (таблицы данных, которые можно индексировать по любому количеству полей)
  • Кеши

Каждая программа должна управлять данными

Программы пишутся для управления данными. Ваша программа может считывать список имен из файла, запрашивать у пользователя некоторые данные через графический интерфейс пользователя или загружать данные с внешнего аппаратного устройства. Но как только данные находятся в вашей программе, вы должны следить за ними. Функции и переменные, которые вы используете для управления данными, называются структурами данных или контейнерами.

Если вы пишете код на языке C, вы обнаружите, что в нем довольно мало сложных структур данных. Конечно, существует множество простых способов хранения данных:

  • Примитивные типы -- int s, float, char и т. д.
  • enum, который может содержать ряд символических имен для целых чисел.
  • Массив — самая гибкая структура данных в C.

Массив может содержать примитивы или серии данных любого типа или указатели на данные любого типа.

Но массивы также имеют много ограничений. Их размер нельзя изменить, поэтому, если вы выделяете память для массива из десяти элементов и обнаруживаете, что вам нужно поместить в него одиннадцать элементов, вам нужно создать новый массив, скопировать в него старые элементы, а затем поместить новый элемент. . Если вы собираетесь перебирать каждый элемент в массиве, вам нужно либо отслеживать, сколько элементов находится в массиве, либо убедиться, что в конце массива есть какой-то маркер «конец массива», чтобы вы знали, когда остановиться.

Проблемы с отслеживанием данных в C многократно решались за счет использования стандартных контейнеров, таких как связанный список и двоичное дерево. Каждый первокурсник, специализирующийся на компьютерных науках, посещает занятия по структурам данных; инструктор обязательно назначит серию упражнений по написанию реализаций этих контейнеров. При написании этих структур учащийся понимает, насколько они сложны; болтающиеся указатели и двойные free подстерегают на каждом углу, чтобы заманить в ловушку неосторожного ученика.

Написание юнит-тестов может сильно помочь, но в целом переписывать одну и ту же структуру данных для каждой новой программы — дело неблагодарное.

В этом помогут встроенные структуры данных. Некоторые языки поставляются со встроенными контейнерами. C++ содержит стандартную библиотеку шаблонов (STL), в которой есть набор классов контейнеров, таких как списки, приоритетные очереди, наборы и карты. Эти контейнеры также являются типобезопасными, что означает, что вы можете поместить только один тип элемента в каждый создаваемый объект-контейнер. Это делает их более безопасными в использовании и устраняет утомительное приведение типов, которое требуется в C. Кроме того, STL содержит множество итераторов, утилит сортировки и т. д., упрощающих работу с контейнерами.

Язык программирования Java также поставляется с набором контейнерных классов. Пакет java.util содержит ArrayList, HashMap, TreeSet и различные другие стандартные структуры. Он также включает в себя утилиты для общей сортировки данных и создания неизменяемых коллекций, а также различные другие полезные функции.

Однако в C нет встроенной поддержки контейнеров; вам придется либо создавать свою собственную, либо использовать чужую библиотеку структур данных.

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

Анализ алгоритма в 100 словах (или меньше)

Разные операции с контейнерами занимают разное время. Например, доступ к первому элементу в длинном списке выполняется намного быстрее, чем сортировка того же списка. Нотация, используемая для описания времени выполнения этих операций, называется O-нотация. Эта тема достойна изучения семестра компьютерных наук, но в двух словах, O-нотация — это наихудший анализ операции. Другими словами, это измерение наибольшего времени, которое потребуется для завершения операции. Это оказалось полезным способом измерения операций со структурой данных, поскольку часто встречаются операции в наихудшем случае (например, когда вы ищете в списке и не находите элемент, который искали).

Ниже продемонстрированы некоторые примеры использования O-нотации, в которых можно использовать набор игральных карт, выложенных в ряд на столе лицевой стороной вниз:

  • O(1) -- Выбор первой карты. O(1) также известен как "постоянное время", потому что получение первой карты занимает одинаковое количество времени, независимо от того, сколько карт находится в списке.
  • O(n) -- Переворачивание каждой карты. O(n) известен как "линейное время", потому что время, необходимое для этого, увеличивается линейно по мере увеличения количества карточек.
  • O(n!) -- Создание списка всех возможных перестановок всех карточек. С каждой новой карточкой, добавляемой в список, количество перестановок увеличивается в несколько раз.

В этом руководстве вы встретите ссылки на O-нотацию операций с различными структурами данных. Знание затрат на конкретную операцию с определенной структурой данных может помочь вам разумно выбирать контейнеры и улучшить производительность вашего приложения.

Компиляция программ GLib

Вы узнаете больше из этого руководства, если будете следовать примерам, компилируя и запуская их. Поскольку они используют GLib, вам нужно сообщить компилятору, где находятся заголовочные файлы и библиотеки GLib, чтобы он мог разрешать определенные в GLib типы. Эта простая программа инициализирует двусвязный список, а затем добавляет к нему строку символов:

//ex‑compile.c
#include <glib.h>
int main(int argc, char∗∗ argv) {
 GList∗ list = NULL;
 list = g_list_append(list, "Hello world!");
 printf("The first item is '%s'\n", g_list_first(list)‑>data);
 return 0;
}

Вы можете компилировать эту программу вызывая GCC примерно так:

$ gcc ‑I/usr/include/glib‑2.0 ‑I/usr/lib/glib‑2.0/include  
   ‑lglib‑2.0 ‑o ex‑compile ex‑compile.c

А затем выполнить собранный экзешник:

$ ./ex‑compile
The first item is 'Hello world!'
$

Однако это довольно трудоемкий вызов GCC. Далее следует более простой способ указать GCC на библиотеки GLib.

Использование pkg-config

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

$ gcc 'pkg‑config ‑‑cflags ‑‑libs glib‑2.0' ‑o ex‑compile ex‑compile.c

ПРИМЕЧАНИЕ.Я попробовал выполнить эту команду в MSYS2 в Windows и получил ошибку. А вот эта команда выполнились:

gcc `pkg-config --cflags glib-2.0` -o glist glist.c `pkg-config --libs glib-2.0`

Обращаю внимание на использование обратных кавычек и раздельные вставки cflag в начале крманды и libs в конце!

Источник информации: https://discourse.gnome.org/t/fail-to-compile-gtk3-both-in-msys2-and-cmd/8470

И вывод такой же, как и раньше:

$ ./ex‑compile
The first item is 'Hello world!'
$

Обратите внимание, что теперь вам больше не нужно указывать пути к заголовочным файлам GLib; Об этом позаботится параметр pkgconfig --cflags. И то же самое касается библиотек, на которые указывает параметр --libs. Конечно, здесь нет никакой магии; pkgconfig просто считывает расположение библиотеки и заголовочного файла из файла конфигурации. В системе Fedora Core 3 файлы pkgconfig расположены в /usr/lib/pkgconfig, а файл glib-2.0.pc выглядит следующим образом:

$ cat /usr/lib/pkgconfig/glib‑2.0.pc
 prefix=/usr
 exec_prefix=/usr
 libdir=/usr/lib
 includedir=/usr/include

 glib_genmarshal=glib‑genmarshal
 gobject_query=gobject‑query
 glib_mkenums=glib‑mkenums

 Name: GLib
 Description: C Utility Library
 Version: 2.4.7
 Libs: ‑L${libdir} ‑lglib‑2.0
 Cflags: ‑I${includedir}/glib‑2.0 ‑I${libdir}/glib‑2.0/include

Таким образом, вся информация просто скрыта слоем косвенности. И если у вас есть дистрибутив Linux, который не поддерживает pkgconfig, вы всегда можете просто вернуться к указанию GCC непосредственно на заголовочные файлы и библиотеки.

 

Односвязные списки

Концепции односвязных списков

Возможно, самым простым контейнером в GLib является односвязный список; GSList. Как следует из названия, это ряд элементов данных, которые связаны друг с другом, так что вы можете переходить от одного элемента данных к другому. Он называется односвязным списком, потому что между элементами существует только одна ссылка. Таким образом, вы можете двигаться только «вперед» по списку, но вы не можете двигаться вперед, а затем назад.

Чтобы углубиться в детали, каждый раз, когда вы добавляете элемент в список, создается новая структура GSList. Эта структура GSList состоит из элемента данных и указателя. Затем предыдущий конец списка указывает на этот новый узел, что означает, что теперь новый узел находится в конце списка. Терминология может немного сбивать с толку, поскольку вся структура называется GSList, и каждый узел также является структурой GSList.

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

Связывание списка элементов имеет некоторые последствия для использования. Определение длины списка — операция O(n); вы не сможете определить длину списка, если не посчитаете каждый элемент. Добавление в начало списка выполняется быстро (операция O(1)), поскольку список не имеет фиксированной длины и не требует перестроения после превышения порогового значения. Но поиск элемента — это операция O(n), поскольку вам нужно выполнить линейный поиск по всему списку, пока вы не найдете то, что ищете. Добавление элемента в конец списка также является операцией O(n), поскольку, чтобы добраться до конца, вам нужно начать с начала и повторять, пока не дойдете до конца списка.

GSList может содержать два основных типа данных: целые числа или указатели. Но на самом деле это означает, что в GSList можно поместить практически все что угодно. Например, если вам нужен GSList с типом данных "short", вы можете просто поместить указатели на шорты в GSList.

На данный момент достаточно теории; на самом деле использовать GSList!

Создание, добавление и удаление

Следующий код инициализирует GSList, добавляет в него два элемента, выводит длину списка и освобождает его:

//ex‑gslist‑1.c
#include <glib.h>
int main(int argc, char∗∗ argv) {
 GSList∗ list = NULL;
 printf("The list is now %d items long\n", g_slist_length(list));
 list = g_slist_append(list, "first");
 list = g_slist_append(list, "second");
 printf("The list is now %d items long\n", g_slist_length(list));
 g_slist_free(list);
 return 0;
}

∗∗∗∗∗ Output ∗∗∗∗∗

The list is now 0 items long
The list is now 2 items long

Несколько замечаний по приведенному выше коду:

  • Большинство функций GLib имеют формат g_(имя контейнера)_(имя функции). Таким образом, чтобы получить длину GSList, вы должны вызвать g_slist_length.
  • Нет функции для создания нового GSList; вместо этого просто объявите указатель на структуру GSList и присвойте ему значение NULL.
  • g_slist_append возвращает новое начало списка, поэтому вам нужно придерживаться этого возвращаемого значения.
  • g_slist_free не волнует, были ли какие-либо элементы помещены в список или нет; Быстрый взгляд на исходный код показывает, что g_slist_free сразу возвращается, если GSList имеет значение NULL. g_slist_length также работает с пустым списком; в этом случае он просто возвращает 0.

 Добавление и последующее удаление данных

Вы можете ввести данные; вам, вероятно, также нужно будет выбрать их. Вот пример:


//ex‑gslist‑2.c

#include <glib.h>

int main(int argc, char∗∗ argv) {

 GSList∗ list = NULL;

 list = g_slist_append(list, "second");

 list = g_slist_prepend(list, "first");

 printf("The list is now %d items long\n", g_slist_length(list));

 list = g_slist_remove(list, "first");

 printf("The list is now %d items long\n", g_slist_length(list));

 g_slist_free(list);

 return 0;

}


∗∗∗∗∗ Output ∗∗∗∗∗


The list is now 2 items long

The list is now 1 items long

Большая часть этого кода должна показаться вам знакомой, но следует учитывать некоторые моменты:


Если вы вызовете g_slist_remove и передадите элемент, которого нет в списке, список не изменится.

g_slist_remove также возвращает новое начало списка.

Вы можете видеть, что "first" добавляется с вызовом g_slist_prepend. Это более быстрый вызов, чем g_slist_append; это O (1), а не O (n), потому что, как упоминалось ранее, выполнение добавления требует полного обхода списка. Поэтому, если вообще удобно использовать g_slist_prepend, вам нужен именно он.

Удаление повторяющихся элементов

Вот морщинка, показывающая, что происходит, когда в списке есть повторяющиеся элементы:


//ex‑gslist‑3.c

#include <glib.h>

int main(int argc, char∗∗ argv) {

 GSList∗ list = NULL;

 list = g_slist_append(list, "first");

 list = g_slist_append(list, "second");

 list = g_slist_append(list, "second");

 list = g_slist_append(list, "third");

 list = g_slist_append(list, "third");

 printf("The list is now %d items long\n", g_slist_length(list));

 list = g_slist_remove(list, "second");

 list = g_slist_remove_all(list, "third");

 printf("The list is now %d items long\n", g_slist_length(list));

 g_slist_free(list);

 return 0;

}


∗∗∗∗∗ Output ∗∗∗∗∗


The list is now 5 items long

The list is now 2 items long

Таким образом, если GSList дважды содержит один и тот же указатель и вы вызываете g_slist_remove, будет удален только первый указатель. Но вы можете удалить все вхождения элемента с помощью g_slist_remove_all.


Последние, n-е и n-е данные

После того, как несколько элементов появятся в GSList, вы можете выбирать их различными способами. Вот несколько примеров с пояснениями в прилагаемых операторах printf:


//ex‑gslist‑4.c

#include <glib.h>

int main(int argc, char∗∗ argv) {

 GSList∗ list = NULL;

 list = g_slist_append(list, "first");

 list = g_slist_append(list, "second");

 list = g_slist_append(list, "third");

 printf("The last item is '%s'\n", g_slist_last(list)‑>data);

 printf("The item at index '1' is '%s'\n", g_slist_nth(list, 1)‑>data);

 printf("Now the item at index '1' the easy way: '%s'\n", g_slist_nth_data(list, 1));

 printf("And the 'next' item after first item is '%s'\n", g_slist_next(list)‑>data);

 g_slist_free(list);

 return 0;

}


∗∗∗∗∗ Output ∗∗∗∗∗


The last item is 'third'

The item at index '1' is 'second'

Now the item at index '1' the easy way: 'second'

And the 'next' item after first item is 'second'

Обратите внимание, что в GSList есть несколько функций быстрого доступа; вы можете просто вызвать g_slist_nth_data вместо того, чтобы вызывать g_slist_nth и затем разыменовывать возвращенный указатель.


Последний оператор printf немного отличается. g_slist_next — это не вызов функции, а скорее макрос. Он расширяется до разыменования указателя ссылки на следующий элемент в GSList. В этом случае вы можете видеть, что мы передали первый элемент в GSList, поэтому макрос расширился, чтобы предоставить второй элемент. Это также быстрая операция, так как нет накладных расходов на вызовы функций.


Шаг назад: работа с пользовательским типом

До сих пор мы работали только со строками; то есть мы только что помещали указатели на символы в GSList. В приведенном ниже примере кода вы определите структуру Person и поместите несколько ее экземпляров в GSList:


//ex‑gslist‑5.c

#include <glib.h>

typedef struct {

 char∗ name;

 int shoe_size;

} Person;

int main(int argc, char∗∗ argv) {

 GSList∗ list = NULL;

 Person∗ tom = (Person∗)malloc(sizeof(Person));

 tom‑>name = "Tom";

 tom‑>shoe_size = 12;

 list = g_slist_append(list, tom);

 Person∗ fred = g_new(Person, 1); // allocate memory for one Person struct

 fred‑>name = "Fred";

 fred‑>shoe_size = 11;

 list = g_slist_append(list, fred);

 printf("Tom's shoe size is '%d'\n", ((Person∗)list‑>data)‑>shoe_size);

 printf("The last Person's name is '%s'\n", ((Person∗)g_slist_last(list)‑>data)‑>name);

 g_slist_free(list);

 free(tom);

 g_free(fred);

 return 0;

}


∗∗∗∗∗ Output ∗∗∗∗∗


Tom's shoe size is '12'

The last Person's name is 'Fred'

Несколько замечаний о работе с GLib и пользовательскими типами:


Вы можете видеть, что размещение определяемого пользователем типа в GSList аналогично вводу строки символов. Учтите также, что при извлечении элемента из списка вам нужно выполнить небольшое преобразование.

В этом примере используется другой макрос GLib — макрос g_new — для создания экземпляра Fred``Person. Этот макрос просто расширяется, чтобы использовать malloc для выделения правильного объема памяти для данного типа, но это немного чище, чем ввод вызова функции malloc вручную.

Наконец, если вы собираетесь выделить память, вам нужно ее освободить. Вы можете видеть, как приведенный выше пример кода использует функцию GLib g_free, чтобы сделать именно это для экземпляра Fred``Person (поскольку он был выделен с помощью g_new). В большинстве случаев g_free просто обертывает обычную функцию free, но GLib также имеет функцию объединения памяти, которую могут использовать g_free и другие функции управления памятью.

Комбинирование, реверсирование и все такое

GSList поставляется с некоторыми удобными вспомогательными функциями, которые могут объединять и реверсировать списки. Вот как они работают:


//ex‑gslist‑6.c

#include <glib.h>

int main(int argc, char∗∗ argv) {

 GSList∗ list1 = NULL;

 list1 = g_slist_append(list1, "first");

 list1 = g_slist_append(list1, "second");

 GSList∗ list2 = NULL;

 list2 = g_slist_append(list2, "third");

 list2 = g_slist_append(list2, "fourth");

 GSList∗ both = g_slist_concat(list1, list2);

 printf("The third item in the concatenated list is '%s'\n", g_slist_nth_data(both, 2));

 GSList∗ reversed = g_slist_reverse(both);

 printf("The first item in the reversed list is '%s'\n", reversed‑>data);

 g_slist_free(reversed);

 return 0;

}


∗∗∗∗∗ Output ∗∗∗∗∗


The third item in the concatenated list is 'third'

The first item in the reversed list is 'fourth'

Как и ожидалось, два списка были объединены "голова к хвосту", так что первый элемент списка list2 стал третьим элементом нового списка. Обратите внимание, что элементы не копируются; они просто зацеплены, так что память нужно освобождать только один раз.


Кроме того, вы можете видеть, что вы можете распечатать первый элемент в перевернутом списке, используя только разыменование указателя (reversed->data). Поскольку каждый элемент в GSList является указателем на структуру GSList, вам не нужно вызывать функцию, чтобы получить первый элемент.


Простая итерация

Вот простой способ перебора содержимого GSList:


//ex‑gslist‑7.c

#include <glib.h>

int main(int argc, char∗∗ argv) {

 GSList∗ list = NULL, ∗iterator = NULL;

 list = g_slist_append(list, "first");

 list = g_slist_append(list, "second");

 list = g_slist_append(list, "third");

 for (iterator = list; iterator; iterator = iterator‑>next) {

  printf("Current item is '%s'\n", iterator‑>data);

 }

 g_slist_free(list);

 return 0;

}


∗∗∗∗∗ Output ∗∗∗∗∗


Current item is 'first'

Current item is 'second'

Current item is 'third'

Объект итератора — это просто переменная, объявленная как указатель на структуру GSList. Это кажется странным, но это то, что вы ожидаете. Поскольку односвязный список представляет собой набор структур GSList, итератор и список должны быть одного типа.


Обратите внимание, что в этом коде используется общая идиома использования GLib; он объявляет переменную итератора одновременно с объявлением самого GSList.


Наконец, выражение выхода из цикла for проверяет, равен ли итератор NULL. Это работает, поскольку NULL будет только после того, как цикл пройдет последний элемент в списке.


Расширенная итерация с функциями

Еще один способ выполнить итерацию по GSList — использовать функцию g_slist_foreach и указать функцию, которая будет вызываться для каждого элемента в списке.


//ex‑gslist‑8.c

#include <glib.h>

void print_iterator(gpointer item, gpointer prefix) {

 printf("%s %s\n", prefix, item);

}

void print_iterator_short(gpointer item) {

 printf("%s\n", item);

}

int main(int argc, char∗∗ argv) {

 GSList∗ list = g_slist_append(NULL, g_strdup("first"));

 list = g_slist_append(list, g_strdup("second"));

 list = g_slist_append(list, g_strdup("third"));

 printf("Iterating with a function:\n");

 g_slist_foreach(list, print_iterator, "‑‑>");

 printf("Iterating with a shorter function:\n");

 g_slist_foreach(list, (GFunc)print_iterator_short, NULL);

 printf("Now freeing each item\n");

 g_slist_foreach(list, (GFunc)g_free, NULL);

 g_slist_free(list);

 return 0;

}


∗∗∗∗∗ Output ∗∗∗∗∗


Iterating with a function:

‑‑> first

‑‑> second

‑‑> third

Iterating with a shorter function:

first

second

third

Now freeing each item

В этом примере много хорошего:


Слово GSList x = g_slist_append(NULL, [независимо]) позволяет объявить, инициализировать и добавить первый элемент в список одним махом.

Функция g_strdup удобна для дублирования строки; просто не забудьте освободить его, когда закончите с ним.

g_slist_foreach позволяет вам передать указатель, поэтому вы можете эффективно передать ему любой аргумент вместе с каждым элементом в списке. Например, вы можете передать аккумулятор и собрать информацию о каждом элементе списка. Единственное ограничение на итерирующую функцию заключается в том, что она принимает в качестве аргумента хотя бы один gpointer; вы можете увидеть, как работает print_interator_short, даже если он принимает только один аргумент.

Обратите внимание, что код освобождает все строки, используя встроенную функцию GLib в качестве аргумента для g_slist_foreach. Все, что вам нужно было сделать в этом случае, это преобразовать функцию g_free в GFunc, чтобы это работало. Обратите внимание, что вам все равно придется освобождать сам GSList с помощью отдельного вызова g_slist_free.

Сортировка с помощью GCompareFunc

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


//ex‑gslist‑9.c

#include <glib.h>

gint my_comparator(gconstpointer item1, gconstpointer item2) {

 return g_ascii_strcasecmp(item1, item2);

}

int main(int argc, char∗∗ argv) {

 GSList∗ list = g_slist_append(NULL, "Chicago");

 list = g_slist_append(list, "Boston");

 list = g_slist_append(list, "Albany");

 list = g_slist_sort(list, (GCompareFunc)my_comparator);

 printf("The first item is now '%s'\n", list‑>data);

 printf("The last item is now '%s'\n", g_slist_last(list)‑>data);

 g_slist_free(list);

 return 0;

}


∗∗∗∗∗ Output ∗∗∗∗∗


The first item is now 'Albany'

The last item is now 'Chicago'

Обратите внимание, что GCompareFunc возвращает отрицательное значение, если первый элемент меньше второго, и 0, если они равно и положительное значение, если второе больше первого. Пока ваша функция сравнения соответствует этой спецификации, она может делать все, что ей нужно внутри.


Кроме того, поскольку многие другие функции GLib следуют этому шаблону, их можно легко делегировать. Фактически, в приведенном выше примере вы можете так же легко заменить вызов my_comparator чем-то вроде g_slist_sort(list, (GCompareFunc)g_ascii_strcasecmp), и вы получите те же результаты.


Поиск элемента

Есть несколько способов найти элемент в GSList. Вы уже видели, как можно просто перебирать содержимое списка, сравнивая каждый элемент, пока не найдете целевой элемент. Вы можете использовать g_slist_find, если у вас уже есть данные, которые вы ищете, и вы просто хотите добраться до нужного места в списке. Наконец, вы можете использовать g_slist_find_custom, который позволяет использовать функцию для проверки каждого элемента в списке. g_slist_find и g_slist_find_custom показаны ниже:


//ex‑gslist‑10.c

#include <glib.h>

gint my_finder(gconstpointer item) {

 return g_ascii_strcasecmp(item, "second");

}

int main(int argc, char∗∗ argv) {

 GSList∗ list = g_slist_append(NULL, "first");

 list = g_slist_append(list, "second");

 list = g_slist_append(list, "third");

 GSList∗ item = g_slist_find(list, "second");

 printf("This should be the 'second' item: '%s'\n", item‑>data);

 item = g_slist_find_custom(list, NULL, (GCompareFunc)my_finder);

 printf("Again, this should be the 'second' item: '%s'\n", item‑>data);

 item = g_slist_find(list, "delta");

 printf("'delta' is not in the list, so we get: '%s'\n", item ? item‑>data : "(null)");

 g_slist_free(list);

 return 0;

}


∗∗∗∗∗ Output ∗∗∗∗∗


This should be the 'second' item: 'second'

Again, this should be the 'second' item: 'second'

'delta' is not in the list, so we get: '(null)'

Обратите внимание, что g_slist_find_custom также принимает указатель на что-либо в качестве второго аргумента, поэтому при необходимости вы можете что-то передать чтобы помочь функции поиска. Кроме того, функция GCompare является последним аргументом, а не вторым аргументом, поскольку она находится в g_slist_sort. Наконец, неудачный поиск возвращает NULL.


Расширенное добавление со вставкой

Теперь, когда вы видели GCompareFunc несколько раз, некоторые из наиболее интересных операций вставки станут более понятными. Элементы можно вставлять в заданную позицию с помощью g_slist_insert, перед указанным элементом с помощью g_slist_insert_before и в отсортированном порядке с помощью g_slist_insert_sorted. Вот как это выглядит:


//ex‑gslist‑11.c

#include <glib.h>

int main(int argc, char∗∗ argv) {

 GSList∗ list = g_slist_append(NULL, "Anaheim "), ∗iterator = NULL;

 list = g_slist_append(list, "Elkton ");

 printf("Before inserting 'Boston', second item is: '%s'\n", g_slist_nth(list, 1)‑>data);

 g_slist_insert(list, "Boston ", 1);

 printf("After insertion, second item is: '%s'\n", g_slist_nth(list, 1)‑>data);

 list = g_slist_insert_before(list, g_slist_nth(list, 2), "Chicago ");

 printf("After an insert_before, third item is: '%s'\n", g_slist_nth(list, 2)‑>data);

 list = g_slist_insert_sorted(list, "Denver ", (GCompareFunc)g_ascii_strcasecmp);

 printf("After inserting 'Denver', here's the final list:\n");

 g_slist_foreach(list, (GFunc)printf, NULL);

 g_slist_free(list);

 return 0;

}


∗∗∗∗∗ Output ∗∗∗∗∗


Before inserting 'Boston', second item is: 'Elkton '

After insertion, second item is: 'Boston '

After an insert_before, third item is: 'Chicago '

After inserting 'Denver', here's the final list:

Anaheim Boston Chicago Denver Elkton

Поскольку g_slist_insert_sorted принимает GCompareFunc, встроенную функцию GLib легко использовать повторно. g_ascii_strcasecmp. И теперь вы можете понять, почему в конце каждого элемента есть дополнительный пробел; это сделано для того, чтобы другой пример g_slist_foreach мог прокрасться туда в конце примера кода, на этот раз с printf в качестве GFunc.


Реальное использование односвязных списков

Вы можете найти много случаев использования GSList во всех трех реальных приложениях с открытым исходным кодом, упомянутых ранее. Большая часть использования довольно проста, с множеством вставок, добавлений, удалений и так далее. Но вот некоторые из наиболее интересных вещей.


Gaim использует GSLlists для хранения текущих разговоров и для различных целей в большинстве плагинов:


gaim-1.2.1/src/away.c использует отсортированный GSList для хранения "сообщений об отсутствии" (сообщений, полученных, когда клиент находится в автономном режиме). Он использует пользовательский GCompareFunc, sort_awaymsg_list, чтобы сортировать эти сообщения по имени отправителя.

gaim-1.2.1/src/protocols/gg/gg.c использует GSList для хранения списка разрешенных учетных записей; затем он использует пользовательский поиск, чтобы убедиться, что учетная запись находится в этом списке. Пользовательский поиск просто делегирует g_ascii_strcasecmp, так что вполне возможно, что его можно полностью исключить и передать g_ascii_strcasecmp напрямую в g_slist_find_custom.

Evolution также использует множество GSLlist:

evolution-data-server-1.0.2/calendar/libecal/e-cal-component.c использует GSList для хранения участников собрания. В одном случае он создает GSList, повторно вызывая g_slist_prepend и заканчивая g_slist_reverse, чтобы получить элементы в нужном порядке. Как упоминалось ранее, это быстрее, чем добавление элементов с помощью g_slist_append.

evolution-2.0.2/addressbook/gui/contact-editor/e-contact-editor.c использует g_slist_find в своего рода защитном предложении; он использует его в обработчике сигнала, чтобы гарантировать, что EContactEditor, полученный в обратном вызове сигнала, все еще существует, прежде чем передать его в качестве аргумента функции.

GIMP использует GSList и в некоторых приятных случаях:

gimp-2.2.4/plug-ins/maze/algorithms.c использует GSList для отслеживания ячеек в алгоритме генерации лабиринта.

gimp-2.2.4/app/widgets/gimpclipboard.c использует GSList для хранения форматов пиксельного буфера буфера обмена (таких как PNG и JPEG); он передает пользовательскую функцию GCompareFunc в g_slist_sort.

gimp-2.2.4/app/core/gimppreviewcache.c использует GSList как своего рода очередь на основе размера; он содержит предварительные просмотры изображений в GSList и использует g_slist_insert_sorted для вставки меньших изображений в первую очередь. Другая функция в том же файле очищает кеш, перебирая тот же GSList и сравнивая каждый элемент по площади поверхности, чтобы найти наименьший элемент для удаления.

 

 

 

 

четверг, 26 мая 2022 г.

Простой пример использования регулярных выражений в языке Python.

 Простой пример использования регулярных выражений в языке Python.

Допустим мы имеем строку с отчетом о количестве зараженных за сегодняшний день. И мы хотим выделить их по тексту в отчете. Пример строки в переменной data.

data="2 716 141 +1215 Случай заражения….строка продолжается"


dt = data.replace(" ","") # Удаляем все пробелы

ss = dt.replace("\t","") # Если есть символы табуляции тоже удаляем

dt = ss

zar = re.search('(Случа(ев|й|я)заражения)', dt) # Так как окончание в слове Случай

# зависит от значения числа, то мы ищем такое словосочетание;

# первые скобки указывают : выбрать конкретное словосочетание из строки

# вторые скобки указывают, что вместо скобок надо подставить буквы ев или й

# или я.

ds = dt.split(zar.group(0)) # результат поиска zar.group(0) содержит конкретное

# словосочетание Случай заражения. После расщепления исходной

# строки по найденному словосочетанию в переменной ds[0]

# находятся искомые числа

nmz = re.findall('[0-9]+', ds[0]) # выбираем эти числа; их два — разделитель символ +

# символ плюс в команде говорит о том, что цифр в числах много

record['zar_number']=nmz[0]

record['zar_delta'] =nmz[1]

воскресенье, 27 февраля 2022 г.

 HD Mesh Scenery v4

Довольно часто люди ломают установку и не видят/не используют HD Mesh Scenery (хотя так думают). Существует очень простой и прямой способ (кроме очевидных или менее очевидных визуальных подсказок) проверить, действительно ли X-Plane загружает HD Mesh Scenery (и, следовательно, ваша установка была правильной).

После - надеюсь - правильной установки и корректной настройки scenery_packs.ini (см. выше) запустите X-Plane в том месте, где вы добавили покрытие HD Mesh Scenery (конечно, нет смысла проверять там, где его нет) !
Когда X-Plane загрузит ваш аэропорт и вы увидите пейзаж, снова аккуратно закройте X-Plane (это гарантирует, что действительно все было записано в Log.txt).
Теперь откройте файловый браузер, перейдите в корневой каталог, где установлен X-Plane, и откройте файл Log.txt (именно там X-Plane регистрирует все хорошее или плохое).
Найдите текст, например «zzz_hd_global_scenery4» (или ТОЧНОЕ имя, которое вы назвали установочной папкой внутри «Custom Scenery»).
Если вы найдете несколько строк, которые выглядят как «Время загрузки DSF: 5940727 для файла Custom Scenery/zzz_hd_global_scenery4/Earth nav data/+20+080/+26+087.dsf (1633010 tris)» (конечно, точное имя файла dsf , и цифры будут меняться в зависимости от места которое вы загрузили) значит все ОК! X-Plane действительно загружает файлы DSF (Scenery) из вашей установки HD Mesh Scenery.
Если вы НЕ найдете такой строки с упоминанием загрузки HD Mesh Scenery v4 DSF (с «Время загрузки DSF…. для файла Custom Scenery/zzz_hd_global_scenery4/Earth nav data/…»), значит, что-то пошло не так, и вам следует внимательно перечитать Инструкция по установке!

вторник, 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 - любитель, Фото любитель со стажем

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

Архив блога