Кратко о PyGI

Оригинал статьи(Eng): http://live.gnome.org/PyGObject/IntrospectionPorting

Ввиду отсутствия PyGTK для Python 3 – в третьей версии нужно использовать PyGI.

Содержание

  1. Как работает PyGI?
  2. Отличия от API дл C
    1. Конструкторы
    2. Передача массивов
    3. Аргументы на выходе функций
    4. GDestroyNotify
    5. Отсутствующие методы/функции
  3. Перегрузки
  4. Портирование с PyGTK 2 на PyGI GTK 3
    1. Шаг 1: Великое Переименование
    2. Шаг 2: Нанести, смыть, повторить
    3. Шаг 3: Изменения в пакетах
  5. Примеры
  6. Комментарии
  7. Известные проблемы

Как работает PyGI?

Поддержка PyGI была добавлена в pygobject версии 2.19.0 (Август 2009), но спецификации стабилизовались только в в версии 2.28, поэтому для выполнения примеров вам нужна как минимум эта версия, и соответствующие версии GTK и других используемых вами библиотек.

pygobject предоставляет пространство имён gi.repository в котором на лету генерируются виртуальные модули Python. Например если у вам установлена GIR для GTK 3, то вы можете выполнить:

  •  $ python -c 'from gi.repository import Gtk; print Gtk'
     <gi.module.DynamicModule 'Gtk' from '/usr/lib/girepository-1.0/Gtk-3.0.typelib'>

и использовать это как обычный модуль Python.

Неожиданный первый пример:

  •  $ python -c 'from gi.repository import Gtk; Gtk.MessageDialog(None, 0, Gtk.MessageType.INFO, Gtk.ButtonsType.CLOSE, "Hello World").run()'

Давайте посмотрим на соответствующее объявление на языке C:

  •  GtkWidget* gtk_message_dialog_new (GtkWindow *parent, GtkDialogFlags flags, GtkMessageType type, GtkButtonsType buttons, const gchar *message_format, ...);

И вызов на C:

  •   GtkMessageDialog* msg = gtk_message_dialog_new (NULL, 0, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "Hello World");
      msg.run()

Итак, что мы здесь видим?

  1. C API во многом похож на Python API(и других языков использующих привязки GI), по структуре, порядку и типам аргументов. Есть несколько исключений, в основном благодаря другому способу работы в Python, в некоторых случаях на Python писать проще; смотрите подробности ниже. Помните, что вы можете (и должны) использовать более полную документацию для C API. Devhelp ваш друг!
  2. Так как Python объектно-ориентированный язык, pygobject (и библиотека GI) представляет GObject API как классы, объекты, методы и атрибуты. Например в Python вы пишете
     b = Gtk.Button(...)
     b.set_label("foo")

    вместо синтаксиса C

      GtkWidget* b = gtk_button_new(...);
      gtk_button_set_label(b, "foo");
  3. Имена классов в библиотеке совпадают с аналогичными в библиотеке для C, за исключением префиксов пространства имён (здесь Gtk), так как они являются именем модуля..
  4. Глобальные константы могут приводить к хаосу в пространствах имён в Python, поэтому pygobject использует пространства имён для их представления. Например, если конструктор MessageDialog ожидает константу типа GtkMessageType, в Python используется класс Gtk.MessageType со своими собственными константами и атрибутами, Gtk.MessageType.INFO.
  5. Типы данных изменены знакомым образом, т.е когда C API ожидает указатель на массив int*, вы можете использовать обычный массив Python [0, 1, 2]. Строка Python "foo" заменяет gchar*, Python None совпадает с  NULL, и т.д. Итак, GObject API естественно переводит типы данных для настоящего объектно-ориентированного языка, такого как Python, и после некоторого времени использования, вы не будете иметь проблем с использованием документации C API для Python.

Всё это не ограничивается GTK, GNOME, или UI. Например, если вы работаете с устройствами, вы вероятно захотите отправлять запросы к udev, который имеет хорошую интеграцию с glib (с сигналами) через библиотеку gudev. Этот пример показывает список всех блочных устройств (например HDD, USB флешек, и т.д.):

  •  $ python
    >>> from gi.repository import GUdev
    >>> c = GUdev.Client()
    >>> for dev in c.query_by_subsystem("block"):
    ...     print dev.get_device_file()
    ...
    /dev/sda
    /dev/sda1
    /dev/sda2
    [...]

Смотрите Документацию GUDevClient для соответствующего C API. GI не ограничивается GObject, вы можете использовать не ООП API, например /usr/share/gir-1.0/xlib-2.0.gir (находится в состоянии разработки). Они также будут представлены как нормальные функции Python (или других языков).

 

Отличия от C API

Структура аргументов методов во многом одинакова. Есть несколько исключений о которых вам нужно знать:

 

Конструкторы

Одно из самых крупных отличий это конструкторы. Есть два способа их вызова:

  • Использовать реальную реализацию из библиотеки. В отличие от стиля Python, вам нужно явно указывать имя конструктора:
     Gtk.Button.new()
     Gtk.Button.new_with_label("foo")
  • Использовать конструктор GObject, передать в него именованные аргументы:
       Gtk.Button(label="foo", use_underline=True)

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

 

Передача массивов

В отличие от C, высокоуровневые языка сохраняют информацию о длинне массивов, которую в C API необходимо явно указывать отдельным аргументом. Python автоматически предоставляет массивы в правильном формате, без нужды указывать дополнительный аргумент None или len(my_array).

Например, в C вы имеете

  •  gtk_icon_theme_set_search_path (GtkIconTheme *icon_theme, const gchar *path[], gint n_elements)

В Python этот вызов сокращается до

  •  my_icon_theme.set_search_path(['/foo', '/bar'])

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

 

Аргументы на выходе функций

Функции C не возвращают более чем один аргумент, поэтому они часто испльзуют указатели. В Python же указатели не используются, но есть возможность легко вернуть более чем одно значение в кортеже. Например:

  •   GdkWindow* gdk_window_get_pointer (GdkWindow *window, gint *x, gint *y, GdkModifierType *mask)

В Python вы вызываете:

  •   (ptr_window, x, y, mask) = mywindow.get_pointer()

 

GDestroyNotify

Некоторые функции GLib/GTK принимают callback методы дополнительным аргументом user_data. В C он часто принимают функцию GDestroyNotify, которая вызывается после выполнения обратной связи, для освобождения памяти. В Python используется автоматическое управление памятью, pygobject позаботится обо всём сам, поэтому просто не указывайте аргумент GDestroyNotify. Например:

  •  void gtk_enumerate_printers (GtkPrinterFunc func, gpointer user_data, GDestroyNotify destroy, gboolean wait)

В Python этот вызов выглядит так

  •  Gtk.enumerate_printers(my_callback, my_user_data, True)

 

Отсутствующие методы/функции

Когда вы проработаете с PyGI долгое вреся, вы наверняка заметите что есть методы для которых не существует привязок. Они обычно отмечаются с помощью  introspectable="0" в GIR.

В лучшем случае это из-за небезопасных вещей, которые GI отключает во избежание падений. Они обычно появляются с соответсвующим предупреждающим сообщением от g-ir-scanner, и обычно их просто исправить.

В другом случае, это функции принимающие переменное число аргументов, таких как gtk_cell_area_add_with_properties(). Переменные аргументы не могут безопасно обрабатываться в libgirepository. В этих случаях обычно есть альтернатива (такая как gtk_cell_area_cell_set_property()). В других случаях библиотеки теперь часто имеют контрфункцию ..._v() которая принимает список аргументов.

 

Перегрузки

Особенность pygobject это возможность заменять функции, методы или классы сторонним кодом, названном “перегрузки”(Overrides).  Одно из основных предназначений этого – предоставление замены для отсутствующих методов. Для примера, метод Gtk.Menu.popup() отсутствует в GTK, но GTK перегружает использование метода Gtk.Menu.popup_for_device(), что в этом случае позволяет быть ближе к оригинальному API. Другой важный случай – это автоматическое преобразование типов данных, например для передачи объектов unicode, когда ожидается UTF-8 gchar*. Также это помогает предотвратить костыли в коде и держать API в чистоте. И наконех перегрузки используются для упращения разработки. Например довольно затруднительно делать вызовы GDBus или конструкции GVariant с родными Gio/GLib API. pygobject с помощью перегрузок делает всё более простым, и стильным, без разрушения оригинального API.

Перегрузки довольно просты для понимания. В основном вам не нужно знать о них, большинство из них обычно используется для того чтобы всякие вещи работали как ожидается. :-)

 

Портирование с PyGTK 2 на PyGI GTK 3

Для  более подробной информации смотрите документацию по миграции GTK2 → GTK3(возможно будет переведена позднее).

Если мы сравним код PyGTK и PyGI для сообщения “Hello”, мы заметим что они похоже по структуре:

  •  $ python -c 'import gtk; gtk.MessageDialog(None, 0, gtk.MESSAGE_INFO, gtk.BUTTONS_CLOSE, "Hello World").run()'

против.

  •  $ python -c 'from gi.repository import Gtk; Gtk.MessageDialog(None, 0, Gtk.MessageType.INFO, Gtk.ButtonsType.CLOSE, "Hello World").run()'

Так, PyGTK также представляет функции C как настоящие классы и методы, благодаря этому структура обычно остаётся такой-же.

 

Шаг 1: Великое Переименование

Основная часть изменений в коде связана с переименованием. Например gtk.* теперь называется Gtk.*, и gtk.MESSAGE_INFO становится Gtk.MessageType.INFO. Похожим образом должны быть изменены импорты: import gtk становится from gi.repository import Gtk.

К счастью, эта механическая задача может быть автоматизирована. pygobject git tree имеет скрипт pygi-convert.sh, который представляет собой список perl’овых строк -pe ‘s/old/new/’ .

Это действительно примитивно, но удивительно эффективно, и есть шанс что небольшие приложения заработают сразу после этого. Заметьте, что работа над скриптом продолжается, и если вы имеете мнение, что в него нужно что-либо добавить, не стесняйтесь добавить баш, или сообщить о нём в IRC (#python on irc.gnome.org).

Когда вы запустите pygi-convert.sh в корне вашего проекта он обработает все ваши файлы *.py. Если у вас там есть другой код Python, названный по другому (как bin/myprogram),вам нужно запустить скрипт ещё раз, с именем файла в виде аргумента.

  • Убедитесь что вы не продолжаете использовать статические привязки к библиотеке, которую вы используете через интроспекцию. Это заставит обёртки обеих привязок смешаться, и возможно возникновение проблем.

 

Шаг 2: Нанести, смыть, повторить

После переименования, начинается самая трудоёмкая часть. Python концептуально не имеет проверки во время компиляции, и не может проверять существование вызываемых методов, или параметры к методам, вы должны войти в цикл “Запустить программу”, “Покликать пока она не упадёт”, “Исправить”, “goto 1″.

Необходимые изменения действительно сложно обобщить, поскольку они сильно зависят от того что делает ваша программа. Например частое использование вызовов pack_start()/pack_end(). В PyGTK они имеют стандартные значения для атрибутов expand, start, и padding но в GTK они отсутствуют, и их также нет в PyGi.

Заметьте, что вы не можете портировать программу на половину. Если вы попытаетесь импортировать и  gtk и gi.repository.Gtk, вы не получите предупреждающих сообщений, но ваша программа будет падать.  Вы можете смешивать статические и GI привязки к другим библиотекам, таким как dbus-python и gi.repository.Gtk.

Если ваше приложение использует дополнение, вы можете использовать libpeas. Это библиотека дополнений к GObject, которая поддерживает языки C, Python и Javascript через интроспекцию.

 

Шаг 3: Изменения в пакетах

После того как ваш код заработал с PyGI и вы добавили его в свою ветку, вам нужно обновить зависимости для вашего пакета. Вы должны просмотреть свой код на “gi.repository” и собрать список всех импортированных библиотек, затем перевести их в имена пакетов. Например если вы импортируете “Gtk, Notify, Gudev”вам необходимо добавить зависимости:

  • Debian/Ubuntu распространяют их в отдельных пакетах, названных gir<GI_ABI_version>-<libraryname>-<library_ABI_version>, для этого примера это gir1.2-gtk-3.0, gir1.2-notify-0.7, и gir1.2-gudev-1.0. Вы можете узнать это с помощью аналогов dpkg -S /usr/lib/girepository-1.0/Gtk-3.0.typelib.
  • Fedora распространяет библиотеки типов вместе с разделяемыми, для данного примера это  gtk3, libgudev1, libnotify. Вы можете узнать о них с помощью аналогов rpm -qf /usr/lib/girepository-1.0/Gtk-3.0.typelib.

В то-же время вы должны выкинуть старые статические зависимости, такие как python-gtk2, python-notify, и другие.

Наконец вам нужно указать версию pygobject в (>= 2.28) для того чтобы использовать более стабильный PyGI.

 

Примеры

 

Комментарии

  • Какие версии пакетов python нам нужны, и как их получить для различных дистрибутивов (gentoo, deb-based, rpm-based…?)
  • Можем ли мы просто откатиться к import gtk, gdk…. если мы находим старые пакеты, или нужно использовать какие-нибудь костыли?

Одна из самых больших трудностей в портировании, это то что константы сменили имена. Скрипт pygi-convert это хорошая идея, но она имеет несколько проблем. Например если в старом коде была запись “gtk.TREE_VIEW_COLUMN_AUTOSIZE” и вы не знаете, что должно быть в новом коде, ищите в /usr/share/gir-1.0/Gtk-2.0.gir  “COLUMN_AUTOSIZE”, и вы найдёте код похожий на этот:

 

    <enumeration name="TreeViewColumnSizing"
                 glib:type-name="GtkTreeViewColumnSizing"
                 glib:get-type="gtk_tree_view_column_sizing_get_type"
                 c:type="GtkTreeViewColumnSizing">
      <member name="grow_only"
              value="0"
              c:identifier="GTK_TREE_VIEW_COLUMN_GROW_ONLY"
              glib:nick="grow-only"/>
      <member name="autosize"
              value="1"
              c:identifier="GTK_TREE_VIEW_COLUMN_AUTOSIZE"
              glib:nick="autosize"/>
      <member name="fixed"
              value="2"
              c:identifier="GTK_TREE_VIEW_COLUMN_FIXED"
              glib:nick="fixed"/>
    </enumeration>

Просмотрев его, вы можете узнать новое имя константы. Оно начинается с “Gtk”, затем имя перечисления (“TreeViewColumnSizing”), и затем имя члена перечисления БОЛЬШИМИ БУКВАМИ (“AUTOSIZE”). Таким образом, мы узнали что старое имя “gtk.TREE_VIEW_COLUMN_AUTOSIZE” изменено на “Gtk.TreeViewColumnSizing.AUTOSIZE”

 

Известные проблемы

  • Прослушивание сигналов: если вы слушаете сигнал “size-allocate” и получаете тип GdkRectangle вместо CairoRectangleInt, это можно исправить с помощью перегрузки сигнала.

PyGObject/IntrospectionPorting (last edited 2011-04-13 08:07:26 by MartinPitt)

Перевёл Сергей Завгородний, 5 Февраля 2012.

 

Оставить комментарий

Ваш email не будет опубликован. Обязательные поля отмечены *

Вы можете использовать это HTMLтеги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>