Содержание страницы
Программирование с curses на Python¶Curses Programming with Python
| Автор: | A.M. Kuchling, Eric S. Raymond |
|---|---|
| Версия: | 2.03 |
Аннотация
Этот документ описывает, как создавать текстовые программы на Python 2.x, используя расширение curses для управления дисплеем.
Что такое curses?¶What is curses?
Библиотека curses предоставляет терминально-независимые средства рисования на экране и обработки клавиатуры для текстовых терминалов; к таким терминалам относятся VT100, консоль Linux и симулированные терминалы, предоставляемые программами X11, такими как xterm и rxvt. Дисплейные терминалы поддерживают различные управляющие коды для выполнения типовых операций, таких как перемещение курсора, прокрутка экрана и очистка областей. Разные терминалы используют сильно отличающиеся коды и часто имеют свои мелкие особенности.
В мире X-дисплеев можно спросить: «зачем всё это?» Действительно, символьные терминалы – устаревшая технология, но есть области, где умение делать с ними нечто необычное всё ещё ценно. Одна из таких областей – маломощные или встраиваемые Unix-системы, не имеющие X-сервера. Другая – инструменты вроде установщиков ОС и конфигураторов ядра, которые могут запускаться до того, как станет доступен X.
Библиотека curses скрывает все детали разных терминалов и предоставляет программисту абстракцию дисплея, содержащую несколько неперекрывающихся окон. Содержимое окна можно менять различными способами – добавлять текст, стирать его, менять внешний вид – а curses автоматически определит, какие управляющие коды нужно отправить на терминал для получения правильного вывода.
Библиотека curses изначально была написана для BSD Unix; более поздние версии System V от AT&T добавили множество улучшений и новых функций. BSD curses больше не поддерживается, его заменил ncurses – реализация интерфейса AT&T с открытым исходным кодом. Если вы используете Unix с открытым исходным кодом, такой как Linux или FreeBSD, ваша система почти наверняка использует ncurses. Поскольку большинство современных коммерческих версий Unix основаны на коде System V, все описанные здесь функции, вероятно, будут доступны. Однако более старые версии curses, входящие в некоторые проприетарные Unix-системы, могут поддерживать не всё.
Никто не портировал модуль curses на Windows. На платформе Windows попробуйте модуль Console, написанный Фредриком Лундом. Модуль Console обеспечивает вывод текста с адресацией по курсору, а также полную поддержку ввода с мыши и клавиатуры, и доступен по адресу http://effbot.org/zone/console-index.htm.
Модуль curses в Python¶The Python curses module
Модуль Python – довольно простая обёртка над C-функциями, предоставляемыми curses; если вы уже знакомы с программированием curses на C, перенести эти знания на Python очень легко. Самое большое отличие в том, что интерфейс Python упрощает дело, объединяя разные C-функции, такие как addstr(), mvaddstr(), mvwaddstr(), в единый метод addstr(). Подробнее об этом будет рассказано позже.
Данный HOWTO – просто введение в написание текстовых программ с использованием curses и Python. Он не претендует на роль полного руководства по API curses; для этого смотрите раздел о ncurses в руководстве по библиотеке Python и справочные страницы C для ncurses. Однако он даст вам основные понятия.
Запуск и завершение приложения curses¶Starting and ending a curses application
Прежде чем что-либо делать, curses необходимо инициализировать. Это делается вызовом функции initscr(), которая определит тип терминала, отправит на терминал необходимые коды настройки и создаст различные внутренние структуры данных. В случае успеха initscr() возвращает объект окна, представляющий весь экран; обычно он называется stdscr, по имени соответствующей переменной C.
import curses
stdscr = curses.initscr()
Обычно curses-приложения отключают автоматический эхо-вывод нажатий на экран, чтобы можно было читать клавиши и отображать их только при определённых условиях. Для этого требуется вызвать функцию noecho().
curses.noecho()
Приложениям также часто требуется мгновенно реагировать на нажатия клавиш, не дожидаясь нажатия Enter; это называется режимом cbreak, в отличие от обычного буферизованного ввода.
curses.cbreak()
Терминалы обычно возвращают специальные клавиши, такие как клавиши курсора или навигационные клавиши (Page Up, Home), в виде многобайтовых escape-последовательностей. Хотя можно написать приложение, которое будет ожидать такие последовательности и обрабатывать их соответствующим образом, curses может сделать это за вас, возвращая специальное значение, например curses.KEY_LEFT. Чтобы curses выполнял эту работу, необходимо включить режим клавиатуры.
stdscr.keypad(1)
Завершить curses-приложение намного проще, чем запустить. Потребуется вызвать
curses.nocbreak(); stdscr.keypad(0); curses.echo()
чтобы отменить настройки терминала, сделанные curses. Затем вызовите функцию endwin(), чтобы восстановить исходный режим работы терминала.
curses.endwin()
Типичная проблема при отладке приложения curses – испорченный терминал, когда приложение «умирает», не восстановив его предыдущее состояние. В Python это часто происходит, когда в коде есть ошибка и возникает необработанное исключение. Например, при вводе символов они больше не отображаются на экране, что затрудняет работу с оболочкой.
В Python можно избежать этих сложностей и значительно упростить отладку, импортировав модуль curses.wrapper. Он предоставляет функцию wrapper(), которая принимает вызываемый объект. Она выполняет описанную выше инициализацию, а также инициализирует цвета, если поддержка цвета присутствует. Затем она запускает переданный вызываемый объект и в конце корректно деинициализирует всё. Вызываемый объект выполняется внутри блока try-catch, который перехватывает исключения, выполняет деинициализацию curses и затем пробрасывает исключение дальше. Таким образом, ваш терминал не останется в странном состоянии при возникновении исключения.
Окна и Pad’ы¶Windows and Pads
Окна – это основная абстракция в curses. Объект окна представляет прямоугольную область экрана и поддерживает различные методы для отображения текста, его стирания, ввода строк пользователем и так далее.
Объект stdscr, возвращаемый функцией initscr(), представляет собой окно, покрывающее весь экран. Многим программам может потребоваться только это одно окно, но, возможно, вы захотите разделить экран на более мелкие окна, чтобы перерисовывать или очищать их по отдельности. Функция newwin() создаёт новое окно заданного размера, возвращая новый объект окна.
begin_x = 20 ; begin_y = 7
height = 5 ; width = 40
win = curses.newwin(height, width, begin_y, begin_x)
Несколько слов о системе координат, используемой в curses: координаты всегда передаются в порядке y, x, и левый верхний угол окна имеет координаты (0,0). Это нарушает общепринятое соглашение, согласно которому координата x обычно идёт первой. Это досадное отличие от большинства других компьютерных приложений, но так было в curses с самого начала, и теперь уже поздно что-то менять.
Когда вы вызываете метод для отображения или стирания текста, эффект не проявляется на экране немедленно. Это связано с тем, что curses изначально разрабатывался для медленных терминальных соединений со скоростью 300 бод; для таких терминалов минимизация времени перерисовки экрана очень важна. Это позволяет curses накапливать изменения экрана и отображать их наиболее эффективным способом. Например, если ваша программа выводит несколько символов в окне, а затем очищает окно, нет необходимости отправлять исходные символы, так как они никогда не будут видны.
Соответственно, curses требует явного указания на перерисовку окон с помощью метода refresh() объектов окна. На практике это не сильно усложняет программирование с curses. Большинство программ выполняют серию действий, а затем приостанавливаются в ожидании нажатия клавиши или другого действия пользователя. Всё, что нужно сделать – убедиться, что экран перерисован перед паузой в ожидании ввода, просто вызвав stdscr.refresh() или метод refresh() какого-либо другого соответствующего окна.
Pad – это особый случай окна; он может быть больше фактического экрана, и в каждый момент отображается только его часть. Для создания pad требуются только его высота и ширина, а для обновления pad нужно указать координаты области на экране, где будет отображаться его подраздел.
pad = curses.newpad(100, 100)
# Эти циклы заполняют pad буквами; это
# в следующем разделе
for y in range(0, 100):
for x in range(0, 100):
try: pad.addch(y,x, ord('a') + (x*x+y*y) % 26 )
except curses.error: pass
# Отображает часть pad в середине экрана
pad.refresh( 0,0, 5,5, 20,75)
Вызов refresh() отображает участок pad'а в прямоугольнике, простирающемся от координаты (5,5) до координаты (20,75) на экране; левый верхний угол отображаемого участка – это координата (0,0) на pad'е. За исключением этого отличия, pad'ы полностью аналогичны обычным окнам и поддерживают те же методы.
Если на экране несколько окон и pad'ов, есть более эффективный способ, позволяющий избежать раздражающего мерцания при обновлении. Используйте метод noutrefresh() каждого окна для обновления структуры данных, представляющей желаемое состояние экрана; затем измените физический экран в соответствии с желаемым состоянием одним вызовом функции doupdate(). Обычный метод refresh() последним действием вызывает doupdate().
Отображение текста¶Displaying Text
С точки зрения C-программиста, curses иногда может выглядеть как запутанный лабиринт функций, тонко отличающихся друг от друга. Например, addstr() выводит строку в текущей позиции курсора в окне stdscr, в то время как mvaddstr() сначала перемещается к заданным координатам y,x, а затем выводит строку. waddstr() аналогична addstr(), но позволяет указать окно для использования вместо stdscr по умолчанию. mvwaddstr() работает аналогично.
К счастью, интерфейс Python скрывает все эти детали; stdscr – это такой же объект окна, как и любой другой, а методы, подобные addstr(), принимают несколько форм аргументов. Обычно существует четыре различные формы.
| Форма | Описание |
|---|---|
| str или ch | Отобразить строку str или символ ch в текущей позиции |
| str или ch, attr | Отобразить строку str или символ ch, используя атрибут attr в текущей позиции |
| y, x, str или ch | Переместиться в позицию y,x внутри окна и отобразить str или ch |
| y, x, str или ch, attr | Переместиться в позицию y,x внутри окна и отобразить str или ch, используя атрибут attr |
Атрибуты позволяют отображать текст в выделенных формах, например, жирным шрифтом, подчёркиванием, инверсным выводом или цветом. Они будут подробнее объяснены в следующем подразделе.
Функция addstr() принимает строку Python в качестве отображаемого значения, тогда как функции addch() принимают символ, который может быть как строкой Python длиной 1, так и целым числом. Если это строка, то можно отображать только символы с кодами от 0 до 255. SVr4 curses предоставляет константы для расширенных символов; эти константы – целые числа больше 255. Например, ACS_PLMINUS – это символ ±, а ACS_ULCORNER – верхний левый угол рамки (удобно для рисования границ).
Окна запоминают, где был оставлен курсор после последней операции, поэтому если опустить координаты y,x, строка или символ будут отображаться там, где закончилась последняя операция. Курсор также можно перемещать с помощью метода move(y,x). Поскольку некоторые терминалы всегда отображают мигающий курсор, стоит убедиться, что курсор находится в таком месте, где он не будет отвлекать; может сбивать с толку, если курсор мигает в каком-то, казалось бы, случайном месте.
Если вашему приложению вообще не нужен мигающий курсор, можно вызвать curs_set(0), чтобы сделать его невидимым. Эквивалентно, и для совместимости со старыми версиями curses, существует функция leaveok(bool). Когда bool равно true, библиотека curses попытается подавить мигающий курсор, и вам не придётся беспокоиться о том, что он остаётся в неположенных местах.
Атрибуты и цвет¶Attributes and Color
Символы могут отображаться разными способами. Строки состояния в текстовом приложении обычно показываются инверсным видео; средство просмотра текста может выделять определённые слова. curses поддерживает это, позволяя задавать атрибут для каждой ячейки экрана.
Атрибут – это целое число, каждый бит которого представляет отдельный атрибут. Можно попытаться отобразить текст с установленными несколькими битами атрибутов, но curses не гарантирует, что все возможные комбинации доступны или что они визуально различимы. Это зависит от возможностей используемого терминала, поэтому безопаснее придерживаться наиболее распространённых атрибутов, перечисленных здесь.
| Атрибут | Описание |
|---|---|
| A_BLINK | Мигающий текст |
| A_BOLD | Яркий или полужирный текст |
| A_DIM | Текст пониженной яркости |
| A_REVERSE | Инверсный текст |
| A_STANDOUT | Лучший из доступных режимов выделения |
| A_UNDERLINE | Подчёркнутый текст |
Итак, чтобы вывести инверсную строку состояния на верхней строке экрана, можно написать код:
stdscr.addstr(0, 0, "Current mode: Typing mode",
curses.A_REVERSE)
stdscr.refresh()
Библиотека curses также поддерживает цвет на терминалах, которые его отображают. Самый распространённый такой терминал – это, вероятно, консоль Linux, за ней следуют цветные терминалы xterm.
Для использования цвета необходимо вызвать функцию start_color() сразу после вызова initscr(), чтобы инициализировать набор цветов по умолчанию (функция curses.wrapper.wrapper() делает это автоматически). После этого функция has_colors() возвращает TRUE, если используемый терминал действительно может отображать цвет. (Примечание: curses использует американское написание «color» вместо канадского/британского «colour». Если вы привыкли к британскому написанию, придётся смириться с его неправильным написанием ради этих функций.)
Библиотека curses поддерживает конечное количество цветовых пар, каждая из которых содержит цвет переднего плана (или текста) и цвет фона. Получить значение атрибута, соответствующее цветовой паре, можно с помощью функции color_pair(); это значение можно побитово объединить OR с другими атрибутами, такими как A_REVERSE, но, опять же, такие комбинации не гарантированно работают на всех терминалах.
Пример, который выводит строку текста с использованием цветовой пары 1:
stdscr.addstr( "Pretty text", curses.color_pair(1) )
stdscr.refresh()
Как уже говорилось, цветовая пара состоит из цвета переднего плана и фона. start_color() инициализирует 8 основных цветов при активации цветового режима. Это: 0:чёрный, 1:красный, 2:зелёный, 3:жёлтый, 4:синий, 5:пурпурный, 6:голубой и 7:белый. Модуль curses определяет именованные константы для каждого из этих цветов: curses.COLOR_BLACK, curses.COLOR_RED и так далее.
Функция init_pair(n, f, b) изменяет определение цветовой пары n, устанавливая цвет переднего плана f и цвет фона b. Цветовая пара 0 жёстко задана как белый на чёрном и не может быть изменена.
Давайте соберём всё вместе. Чтобы изменить цвет 1 на красный текст на белом фоне, нужно вызвать:
curses.init_pair(1, curses.COLOR_RED, curses.COLOR_WHITE)
При изменении цветовой пары любой уже отображаемый текст с этой парой изменится на новые цвета. Новый текст в этой цветовой паре можно вывести так:
stdscr.addstr(0,0, "RED ALERT!", curses.color_pair(1) )
Очень продвинутые терминалы могут изменять определения реальных цветов на заданное значение RGB. Это позволяет изменить цвет 1, обычно красный, на пурпурный, синий или любой другой по вашему желанию. К сожалению, консоль Linux не поддерживает эту возможность, поэтому я не смог это опробовать и не могу привести примеры. Вы можете проверить, поддерживает ли это ваш терминал, вызвав can_change_color(), которая возвращает TRUE, если такая возможность есть. Если вам повезло иметь такой талантливый терминал, обратитесь к man-страницам вашей системы за дополнительной информацией.
Пользовательский ввод¶User Input
Сама библиотека curses предоставляет лишь очень простые механизмы ввода. Поддержка Python добавляет виджет текстового ввода, который частично компенсирует этот недостаток.
Самый распространённый способ получения ввода от окна – использование его метода getch(). getch() приостанавливает работу и ждёт, пока пользователь нажмёт клавишу, отображая её, если ранее была вызвана echo(). Можно также указать координаты, на которые следует переместить курсор перед паузой.
Это поведение можно изменить с помощью метода nodelay(). После вызова nodelay(1) метод getch() для окна становится неблокирующим и возвращает curses.ERR (значение -1), если нет готового ввода. Также существует функция halfdelay(), которая (по сути) устанавливает таймер на каждый вызов getch(); если в течение заданной задержки (измеряется в десятых долях секунды) ввод не поступает, curses вызывает исключение.
Метод getch() возвращает целое число; если оно находится в диапазоне от 0 до 255, оно представляет ASCII-код нажатой клавиши. Значения больше 255 – это специальные клавиши, такие как Page Up, Home или клавиши курсора. Возвращённое значение можно сравнивать с константами, например curses.KEY_PPAGE, curses.KEY_HOME или curses.KEY_LEFT. Обычно главный цикл программы выглядит примерно так:
while True:
c = stdscr.getch()
if c == ord('p'): PrintDocument()
elif c == ord('q'): break # Выход из while()
elif c == curses.KEY_HOME: x = y = 0
Модуль curses.ascii предоставляет функции определения принадлежности к классу ASCII, которые принимают аргументы как целочисленного типа, так и строки длиной 1 символ; они могут быть полезны для написания более читаемых тестов для ваших интерпретаторов команд. Также предоставляются функции преобразования, принимающие аргументы целочисленного или строкового типа (длиной 1 символ) и возвращающие значение того же типа. Например, curses.ascii.ctrl() возвращает управляющий символ, соответствующий переданному аргументу.
Также есть метод для получения целой строки – getstr(). Он используется нечасто, так как его функциональность весьма ограничена; из доступных клавиш редактирования – только клавиша backspace и клавиша Enter, которая завершает ввод строки. При желании можно ограничить длину строки фиксированным числом символов.
curses.echo() # Включить эхо-вывод символов
# Получить строку из 15 символов, курсор на верхней строке
s = stdscr.getstr(0,0, 15)
Модуль Python curses.textpad предоставляет нечто лучшее. С его помощью можно превратить окно в текстовое поле, которое поддерживает набор сочетаний клавиш в стиле Emacs. Различные методы класса Textbox поддерживают редактирование с проверкой ввода и сбор результатов редактирования как с завершающими пробелами, так и без них. Подробнее см. в документации библиотеки к curses.textpad.
Для получения дополнительной информации¶For More Information
Это руководство не охватило некоторые продвинутые темы, такие как скрапинг экрана или захват событий мыши из экземпляра xterm. Но страница библиотеки Python для модулей curses теперь достаточно полная. Рекомендуется изучить её далее.
Если есть сомнения в детальном поведении любой из точек входа ncurses, обратитесь к man-страницам вашей реализации curses, будь то ncurses или проприетарная версия от поставщика Unix. В man-страницах будут описаны все особенности, а также приведены полные списки всех доступных функций, атрибутов и символов ACS_*.
Поскольку API curses очень велик, некоторые функции не поддерживаются в интерфейсе Python не потому, что их сложно реализовать, а потому, что они пока никому не понадобились. Не стесняйтесь добавить их и затем отправить патч. Кроме того, у нас пока нет поддержки библиотеки меню, связанной с ncurses; не стесняйтесь добавить и её.
Если вы напишете интересную небольшую программу, не стесняйтесь внести её в качестве очередной демонстрации. Мы всегда можем использовать больше таких!
Часто задаваемые вопросы по ncurses: http://invisible-island.net/ncurses/ncurses.faq.html