- Подписка на печатную версию:
- Подписка на электронную версию:
- Подшивки старых номеров журнала (печатные версии)
LXF80:Python
Материал из Linuxformat.
Python |
---|
|
Python для профессионалов |
---|
Python + PyGame |
---|
Python + Web |
---|
Python + Clutter |
---|
УЧЕБНИК Python
ПРОГРАММИРОВАНИЕ СЦЕНАРИЕВ
Содержание |
Уроки Python
ЧАСТЬ 6
Теперь, когда мы научились обрабатывать строки, самое время показать наше умение другим. Но кто будет смотреть сценарий «без лица», пусть даже в Konsole с красивым прозрачным фоном? Для тех, кто не Квентин Тарантино, Сергей Супрунов покажет, как создаются на Python графические интерфейсы.
Чем больше работаешь с Python, тем сильнее ощущаешь его мощь и гибкость. Порой кажется, что для этого языка нет ничего невозможного. Это же можно отнести и к разработке графического интерфейса пользователя (хотя здесь Python просто использует возможности мощных библиотек). Как вы увидите, Python и в этом вопросе сохраняет простоту и эффективность, становясь весьма удобным инструментом для решения таких задач, как создание «обёрток» к различным консольным утилитам, разработка графических конфигураторов для ваших любимых инструментов, проектирование прототипов программ (куда проще за пару дней согласовать с заказчиком все «интерфейсные» моменты разрабатываемого проекта, меняя внешний вид прямо на переговорах, чем спустя месяцы напряжённой работы переделывать многомегабайтный проект на C++, только лишь потому, что заказчик счёл интерфейс слишком сложным для освоения), и т.д. Кстати, инсталлятор Gentoo, появившийся в версии 2006.0 и наделавший столько шума, тоже написан на Python.
Для Python разработано множество инструментов, отличающихся по гибкости, сложности, степени интеграции с вашим окружением. В рамках одного урока сложно рассмотреть всё это многообразие даже в общих чертах. Поэтому мы ограничимся кратким знакомством с модулем Tkinter и «привязками» PyQt и PyGTK.
Есть ещё достаточно мощный и переносимый модуль wxPython, являющийся привязкой к графической библиотеке wxWidgets, который тоже заслуживает внимания, но думаю, вы сможете разобраться с ним самостоятельно.
Начнём со старого доброго Tkinter, который, несмотря на некоторую «неказистость» и плохую интеграцию с системой, всё же остаётся стандартным модулем, входящим в поставку практически всех дистрибутивов. К тому же его простота – идеальная особенность для учебных целей. А разобравшись с ним, вы без труда освоите и другие инструменты.
Очарование простоты
Проверить наличие Tkinter в вашем дистрибутиве достаточно просто (он должен быть, но всякое случается): запустите Python и выполните команду import Tkinter (обратите внимание на заглавную первую букву). Если вам не повезло и вы увидели сообщение об ошибке, придётся установить этот модуль отдельно. Проконсультируйтесь с вашим менеджером пакетов, не знает ли он случайно про Tkinter (имя пакета, в отличие от модуля, обычно записывается только маленькими буквами). Если он окажется из партизанской семьи, то прямая дорога вам на http://www.python.org/ (который с новым дизайном смотрится очень даже приятно).
Разобравшись с модулем, приступим сразу к серьёзной работе. В качестве примера выберем такую задачу: разработать графический интерфейс для просмотра man-страниц. Хорош он тем, что логика предельно проста, и нам почти не придётся отвлекаться от основной задачи. Итак, сразу код (по мере необходимости, будем прерывать его некоторыми пояснениями):
#!/usr/bin/Python # -*- coding: utf-8 -*- import os, re from Tkinter import *
Здесь мы объявили кодировку нашей локали и подключили необходимые для работы модули (не забывайте следить за регистром символов). Далее опишем класс, который будет отвечать за работу нашего приложения. Использование объектного подхода позволит в дальнейшем, если потребуется, легко создавать модифицированные приложения на базе этого класса или интегрировать его функциональность в другие объекты:
class ManReader: def getman(self, manpage, sn): bschar = '\b' tmp = os.popen('man %s %s 2>/dev/null' % (sn, manpage)).read() if tmp: tmp = re.sub(r'(.)%c\1' % bschar, r'\1', tmp) tmp = tmp.replace('_%c' % bschar, '') else: tmp = 'Page "%s" in section "%s" not found' % (manpage, sn or 'Auto') return tmp
Метод с украинским именем getman() отвечает за то, чтобы вернуть запрошенную man-страницу в виде текстовой переменной. Для этого используем уже знакомую нам функцию popen() модуля os. Нужно учитывать, что man-страницы могут содержать специальное форматирование: подсветку и подчёркивание. Чтобы не усложнять наш пример, мы просто избавляемся от ненужных символов, оставляя чистый текст. Пожалуй, нужно пояснить, как мы это делаем.
Если вы посмотрите на результат работы команды man в «натуральном» виде, например, сохранив его в файл, то заметите, что яркость создаётся следующим приёмом: символ выводится, стирается символом «\b» (Backspace) и затем снова выводится. Подчёркивание достигается выводом и последующим стиранием символа «_». То есть вы можете увидеть что-то такое: N\bNA\bAM\bME\bE = NAME,_\b/_\be_\bt_\bc = /etc. С удалением подчёркиваний всё понятно. А вот для удаления «символов яркости» мы используем регулярное выражение, причём на первый фрагмент, взятый в скобки, мы можем в дальнейшем ссылаться с помощью \1. То есть регулярное выражение «(.)!\1» означает два одинаковых символа, разделённых восклицательным знаком. Мы же используем символ \b. Обратите внимание, что его нельзя указывать непосредственно в регулярном выражении, иначе он будет трактоваться как граница слова. Вернёмся к нашим «виджетам»:
def pressBtn(self): sn = sectno.get()[0] if sn == '-': sn = tx.delete(1.0, END) tx.insert(1.0, self.getman(ent.get(), sn))
Это – функция-обработчик нажатия на кнопку (которую мы пока не нарисовали). Ещё ничего не понятно, но мы вернёмся к ней чуть позже, а сейчас перейдем к самому важному методу нашего класса, собственно и рисующему рабочее окно. Разберём его подробнее:
def Drawface(self): global ent, tx, sectno
Объявляем некоторые переменные как глобальные, чтобы мы могли обращаться к ним из других функций (в нашем примере – из pressBtn).
win = Tk() win.title('Просмотрщик man-страниц')
Так мы создаём объект, который будет являться нашим рабочим окном. Заодно задаём ему заголовок.
sectno = StringVar() sectno.set('---Auto---')
В будущем нам понадобится эта переменная – она должна представлять собой особый объект, поэтому и создаём её как экземпляр класса Tkinter.StringVar().
fcmd = Frame(win) fcmd.pack(side=TOP, fill=X)
В основном окне создаём фрейм, который будет являться контейнером для элементов управления. Второй строчкой «упаковываем» его, т.е. указываем место расположения на главном окне. Параметры метода pack() означают: прикрепить элемент к верхнему краю (TOP) и растянуть по горизонтальной оси.
Здесь нужно сказать, что pack – не единственный метод упаковки в Tkinter. В ряде случаев удобнее использовать упаковку «по сетке» – grid (подробности можно узнать в документации). Приступаем к созданию графических элементов:
lbl1 = Label(fcmd, text='Read about ') lbl1.pack(side=LEFT)
Это – обычная текстовая метка. Она размещается во фрейме (первый аргумент метода) и упакована по левому краю.
ent = Entry(fcmd) ent.bind('<Return>', (lambda event: self.pressBtn())) ent.pack(side=LEFT)
Текстовое поле ввода также упаковываем слева, сразу после lbl1.
Метод bind() задаёт реакцию поля на нажатие клавиши [Enter] (a.k.a. Return) – будет вызван метод pressBtn().
lbl2 = Label(fcmd, text=' in section ') lbl2.pack(side=LEFT) slt = OptionMenu(fcmd, sectno, '---Auto---', '1 User Utilities', '2 System Calls', '3 Library Functions', '4 Devices & Kernel Interfaces', '5 File Formats', '6 Games', '7 Macros & SQL Commands', '8 System Utilities', '9 X Window', 'n Built-In commands', ) slt.pack(side=LEFT)
Ещё одна текстовая метка, и далее – выпадающий список. В нём мы перечисляем имеющиеся разделы справки, к которым будет относиться наш запрос.
btn1 = Button(fcmd, command=win.quit, text='Quit') btn1.pack(side=RIGHT) btn2 = Button(fcmd, command=self.pressBtn, text='Open') btn2.pack(side=RIGHT)
Две кнопки – завершающая работу и отображающая запрошенную man-страницу. Обратите внимание, что здесь упаковка выполняется по правому краю, т.е. Quit будет самой правой, а Open – чуть левее. Важнейший параметр – command, задает функцию-обработчик. В первом случае используется встроенный метод quit, завершающий работу, во втором – наш метод pressBtn. Обратите внимание, что здесь происходит не вызов метода, а даётся ссылка на него, т.е. имя указывается без скобок.
fview = Frame(win) fview.pack(side=BOTTOM, fill=BOTH, expand=YES) sb = Scrollbar(fview) tx = Text(fview, relief=SUNKEN) sb.config(command=tx.yview) tx.config(yscrollcommand=sb.set) sb.pack(side=RIGHT, fill=Y) tx.pack(side=TOP, expand=YES, fill=BOTH)
Во втором фрейме, который мы прикрепляем к нижнему краю и растягиваем во все стороны, заполняя всё доступное пространство, размещается текстовое поле, где мы будем отображать содержимое man-страницы. Поскольку последняя может быть достаточно длинной, понадобится скроллинг (элемент Scrollbar). Обратите внимание на то, как методами config() мы обеспечиваем взаимную привязку текстового поля и полосы прокрутки. Параметр relief в описании текстового поля задаёт вид рамки вокруг поля.
Настало время поговорить о pressBtn. Как вы видели, этот метод будет выполняться в двух случаях – по щелчку на кнопке Open и при нажатии [Enter] в поле ввода. Получив цифру раздела справки и очистив текущее содержимое поля tx от начала (1,0) до конца (END), вызываем метод getman() и вставляем результат в tx.
win.mainloop()
Всё выше было просто подготовкой. А вот этой командой мы запускаем наше окно в работу. С этого момента управление передаётся Tkinterобъекту, и влиять на его поведение можно только с помощью описанных ранее обработчиков событий.
if __name__ == '__main__': test = ManReader() test.Drawface()
Ну, это должно быть понятно – если скрипт запускается автономно, а не экспортируется в другой, то создаём объект нашего класса и запускаем его в работу.
Фух… Кажется, мы сделали это. Результат наших трудов можно наблюдать на рисунке (Рисунок 1). Как видите, «виджеты» имеют свой уникальный дизайн и будут резко выделяться на фоне остального интерфейса, внешний вид которого вы с такой любовью выбирали среди десятков различных стилей. Но это работает (причём одинаково) и в KDE, и в Gnome, и даже в Windows и Mac OS X.
Теперь, когда мы разобрались с основами, самое время перейти к «родным» для Linux-окружения средствам – PyQt и PyGTK. Прежде чем приступать к работе, проверьте, есть ли в вашей системе нужные модули.
Пара слов про PyGTK
Мощь библиотеки GTK позволяет создавать таких «гигантов графики», как Gimp. Отрадно, что мы можем в значительной степени воспользоваться её возможностями и в сценариях Python. В каталоге /usr/share/doc/pygtk<версия>/examples вы найдёте массу примеров её использования. Здесь приведём простейший вариант:
#!/usr/bin/python # -*- coding: utf-8 -*- import gtk # Создаём основное окно win = gtk.Window() def main(): # Задаём параметры окна (размер, заголовок и т.д.) win.set_default_size(300, 50) win.set_border_width(10) win.connect('destroy', gtk.main_quit) win.set_title('Небольшой пример') txtvar = 'Библиотека ''GTK ''привносит в ''Python ''небывалую мощь и высокий уровень интеграции в среду Gnome.' # Текстовая метка – не забывайте применять метод show() к каждому объекту, чтобы он был видимым txt = gtk.Label(txtvar) txt.show() # Кнопка. На неё «привязываем» завершение работы btn = gtk.Button('Закрыть') btn.connect('pressed', lambda button: gtk.main_quit()) btn.show() # объект Window может содержать только один элемент, так что им будет HBox, # который служит контейнером для остальных box = gtk.HBox() box.pack_start(txt) box.pack_start(btn) win.add(box) box.show() win.show() gtk.main() main()
Результат представлен на рисунке 2.
Фанатам KDE посвящается
Если для вас Linux не мыслим без KDE, то PyQt – как раз тот инструмент, который способен обеспечить «бесшовную» интеграцию ваших Python-сценариев с окружением рабочего стола. (Впрочем, Qt работает не только в Linux, так что ваши решения по-прежнему сохранят определённый уровень переносимости.) К тому же, для разработки интерфейса к вашим услугам Qt Designer. А создадим мы программу для просмотра запущенных в системе процессов. Итак, в путь!
Делай «РАЗ»!
Первым делом, запустите Qt Designer и откройте новый проект типа Widget. Для кнопки создайте соединение с формой (Connect Signal/ Slots) и объявите новый слот, назвав его showPs. В дальнейшем это будет наш обработчик нажатия кнопки. Сохранив интерфейс (Рисунок 3) под именем psview.ui, нужно конвертировать его в Python-код:
$ pyuic psview.ui > psview.py
Теперь в psview.py описан класс, отвечающий за наш интерфейс. Если вы посмотрите на него, то увидите, что всё не намного сложнее, чем в Tkinter, так что при желании интерфейс можно создать и вручную.
Делай «ДВА»!
Далее, нам нужно довести всё это до ума. Непосредственное редактирование созданного сценария – самое плохое решение, поскольку вы лишитесь возможности корректировать интерфейс в Qt Designer, т.к. все изменения при этом будут потеряны. Поэтому правильно будет оставить psview.py в неприкосновенном виде, а для работы описать ещё один класс на базе созданного, пользуясь возможностями наследования. Просто создать экземпляр класса Form1 тоже не совсем хорошо – ведь нам нужно будет изменить поведение класса, отредактировав обработчик нажатия кнопки. Полученный код с переопределённой функциейобработчиком будет выглядеть так:
#!/usr/bin/Python # -*- coding: utf-8 -*import os, sys # Импортируем модуль, созданный автоматически from psview import * class PsForm(Form1): # наследуем от Form1 def __init__(self): # Помните, что при наследовании инициализацию #- родительского класса нужно делать вручную? Form1.__init__(self) def showPs(self): # Формируем нужные ключи команды ps if self.radioButton1.isChecked(): all = 'a' else: all = # Выводимые поля описываются после ключа «o» #- Очень важно не допускать пробелов! fields = 'o' if self.checkBox1.isChecked(): fields += 'pid,' if self.checkBox2.isChecked(): fields += 'user,' if self.checkBox3.isChecked(): fields += 'stat,' if self.checkBox4.isChecked(): fields += 'command,' fields = fields[:-1] # последнюю запятую – долой! filter = self.lineEdit1.text() # Если есть фильтр, то заголовок придётся забирать #- отдельной командой (head – чтобы не выводить лишнее) if filter: cmd = 'ps %sx%s | grep %s | grep -v grep' % (all, fields, filter) head = os.popen('ps %s | head' % fields).readline() body = os.popen(cmd).read() else: cmd = 'ps %sx%s' % (all, fields) pspipe = os.popen(cmd) head = pspipe.readline() # первая строка body = pspipe.read() # всё остальное pspipe.close() self.tl_header.setText(head) self.te_body.setText(body) # Создаём объект-приложение app = QApplication(sys.argv) form = PsForm() app.setMainWidget(form) form.show() app.exec_loop()
Небольшое пояснение: чтобы при прокрутке заголовок был всегда на виду, мы вынесли его в отдельную текстовую метку (tl_header). Поскольку при использовании grep заголовок теряется, придётся приложить чуточку усилий, чтобы всё-таки обеспечить его вывод (см. фрагмент «if filter – else»).
Делай «ТРИ»!
Всё! Можно запускать нашу оболочку, не забыв сделать скрипт исполняемым (Рисунок 4). Конечно, это не верх совершенства – из множества доступных полей поддерживаются только четыре, нет проверки выражения фильтра на безопасность (а что, если пользователь введёт «root; rm -Rf /»?). Впрочем, это беда любого графического интерфейса – неизбежная потеря функциональности и гибкости в угоду сомнительным удобствам. Тем не менее, наша задача была всё-таки в другом – показать пример разработки ГИП, с чем мы успешно справились.
Как видите, изложенное сегодня – всего лишь основа. Но, надеюсь, этого будет достаточно, чтобы приступить к быстрой и эффективной разработке графических интерфейсов. На этом мы завершаем «Уроки Python», но не прощаемся – со следующего номера начинается серия «Python для профессионалов», где мы поговорим о многопоточных приложениях, обработке сетевых протоколов, взаимодействии с СУБД и библиотеке Python Image Library.