Документация Python неофициальный перевод
Содержание страницы

Программирование с curses на PythonCurses 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 в PythonThe Python curses module

Модуль Python является довольно простой обёрткой над функциями C, предоставляемыми curses; если вы уже знакомы с программированием curses на C, перенести эти знания на Python очень легко. Самое большое отличие в том, что интерфейс Python упрощает вещи, объединяя разные функции C, такие как addstr(), mvaddstr(), mvwaddstr(), в один метод addstr(). Позже это будет рассмотрено подробнее.

Данный HOWTO – просто введение в написание текстовых программ с использованием curses и Python. Он не претендует на роль полного руководства по API curses; для этого смотрите раздел о ncurses в руководстве по библиотеке Python и справочные страницы C для ncurses. Однако он даст вам основные понятия.

Запуск и завершение приложения cursesStarting 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), в виде многобайтовой управляющей последовательности. Хотя можно написать приложение, которое ожидает такие последовательности и обрабатывает их соответствующим образом, curses может сделать это за вас, возвращая специальное значение, например curses.KEY_LEFT. Чтобы поручить curses эту работу, необходимо включить режим keypad.

stdscr.keypad(1)

Завершить curses-приложение намного проще, чем запустить. Потребуется вызвать

curses.nocbreak(); stdscr.keypad(0); curses.echo()

Для отмены настроек терминала, установленных curses. Затем следует вызвать функцию endwin(), чтобы восстановить терминал в его исходный режим работы.

curses.endwin()

Типичная проблема при отладке приложения curses – испорченный терминал, когда приложение «умирает», не восстановив его предыдущее состояние. В Python это часто происходит, когда в коде есть ошибка и возникает необработанное исключение. Например, при вводе символов они больше не отображаются на экране, что затрудняет работу с оболочкой.

В Python можно избежать этих сложностей и значительно упростить отладку, импортировав функцию curses.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(); это значение можно объединить поразрядным ИЛИ с другими атрибутами, такими как 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 1:
    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, которые принимают аргументы в виде целого числа или строки из одного символа; это может быть полезно при написании более читаемых тестов для ваших интерпретаторов команд. Он также предоставляет функции преобразования, которые принимают аргументы в виде целого числа или строки из одного символа и возвращают тот же тип. Например, 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