Curses программирование с Python

Автор:A.M. Kuchling, Eric S. Raymond
Релиз:2.04

Аннотация

В этом документе описывается использование модуля расширения curses для управления отображениями в текстовом режиме.

Что такое curses?

Библиотека curses предоставляет независимое от терминала устройство для раскрашивания на экране и обработки клавиатуры для текстовых терминалов; такие терминалы включают в себя VT100s, консоль Linux и моделируемый терминал, предоставляемый различными программами. Терминалы дисплея поддерживают различный контроль коды, чтобы выполнить общие операции, такие как перемещение курсора, завивание экрана и стирание областей. Различные терминалы используют сильно различающиеся коды, и часто имеют свои собственные мелкие причуды.

В мире графических изображений можно спросить «зачем беспокоиться»? это правда, что терминалы отображения символьных ячеек - устаревшая технология, но есть ниши, в которых возможность делать с ними модные вещи по-прежнему ценна. Одна ниша находится на небольшой площади или встроенных Unixes, которые не работают на сервере X. Другой инструмент, например, установщики оС и конфигураторы ядра, могут работать до того, как будет доступна графическая поддержка.

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

Библиотека curses изначально была написана для BSD Unix; более поздние версии System V Unix от AT&T добавили множество усовершенствований и новых функций. BSD curses больше не сохраняется, будучи замененным ncurses, который является общедоступным внедрением интерфейса AT&T. Если вы используете Unix с открытым исходным кодом, например Linux или FreeBSD, ваша система почти наверняка использует ncurses. Поскольку большинство современных коммерческих версий Unix основаны на коде System V, все описанные здесь функции, вероятно, будут доступны. Тем не менее, старые версии curses, переносимые некоторыми проприетарными Unixes, могут не поддерживать все.

Версия для Windows Python не включает модуль curses. Доступна портированная версия вызываемая UniCurses. Вы могли также пытаться модуль Console написанный Фредриком Ландхом, который не использует тот же API в качестве curses, но обеспечивает адресуемую курсором текстовую продукцию и полную поддержку клавишного входа и мыши.

Модуль Python curses

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

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

Запуск и завершение приложения curses

Прежде, чем сделать что-либо, 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(True)

Завершить приложение curses гораздо проще, чем запустить его. Тебе нужно позвонить:

curses.nocbreak()
stdscr.keypad(False)
curses.echo()

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

curses.endwin()

Распространенная проблема при отладке приложения curses заключается в том, чтобы запутать терминал, когда приложение умирает без восстановления предыдущего состояние терминала. В Python это обычно происходит, когда ваш код багги и вызывает необъяснимое исключение. Клавиши больше не отражаются на экране при их вводе, например, что затрудняет использование оболочки.

В Python вы можете избежать этих осложнений и сделать отладку гораздо проще, импортировав функцию curses.wrapper() и используя ее так:

from curses import wrapper

def main(stdscr):
    # Clear screen
    stdscr.clear()

    # This raises ZeroDivisionError when i == 10.
    for i in range(0, 11):
        v = i-10
        stdscr.addstr(i, 0, '10 divided by {} is {}'.format(v, 10/v))

    stdscr.refresh()
    stdscr.getkey()

wrapper(main)

Функция wrapper() принимает вызываемый объект и выполняет описанные выше инициализации, также инициализируя цвета, если присутствует поддержка цвета. wrapper() затем запускает предоставленный вызываемый. Однажды подлежащая выкупу прибыль, wrapper() восстановит оригинальный состояние терминала. Вызываемый абонент вызывается внутри tryexcept, который ловит исключения, восстанавливает состояние терминала, а затем повторно вызывает исключение. Таким образом, ваш терминал не будет оставлен в смешном состояние на исключение, и вы сможете прочитать сообщение исключения и трейсбэк.

Окна и подушки

Окна - это основная абстракция в 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.LINES и curses.COLS для получения размеров y и x. Юридические координаты будут расширяться от (0,0) до (curses.LINES - 1, curses.COLS - 1).

При вызове метод для отображения или удаления текста эффект не сразу отображается на дисплее. Вместо этого для обновления экрана необходимо вызвать refresh() метод объектов окна.

Это вызвано тем, что curses был первоначально написан с медленными 300-бодовыми предельными связями в памяти; с этими терминалами очень важно минимизировать время, необходимое для перерисовки экрана. Вместо этого curses накапливает изменения на экране и отображает их наиболее эффективным образом при вызове refresh(). Например, если программа отображает текст в окне, а затем очищает окно, нет необходимости отправлять исходный текст, так как он никогда не виден.

На практике явно сообщение curses изменить окно действительно не усложняет программирование с curses очень. Большинство программ переходят в шквал активности, а затем приостанавливают ожидание нажатия клавиши или какого-либо другого действия со стороны пользователя. Все, что вам нужно сделать, это убедиться, что экран был перерисован перед паузой, чтобы дождаться ввода данных пользователем, первым вызовом stdscr.refresh() или refresh() метод какого- либо другого соответствующего окна.

Подушечка - особый случай окна; она может быть больше, чем фактический экран дисплея, и только часть панели, отображаемая одновременно. Создание подушки требует высоты и ширины подушки, в то время как обновление подушки требует указания координат экранной области, где будет отображаться подсекция подушки:

pad = curses.newpad(100, 100)
# These loops fill the pad with letters; addch() is
# explained in the next section
for y in range(0, 99):
    for x in range(0, 99):
        pad.addch(y,x, ord('a') + (x*x+y*y) % 26)

# Displays a section of the pad in the middle of the screen.
# (0,0) : coordinate of upper-left corner of pad area to display.
# (5,5) : coordinate of upper-left corner of window area to be filled
#         with pad content.
# (20, 75) : coordinate of lower-right corner of window area to be
#          : filled with pad content.
pad.refresh( 0,0, 5,5, 20,75)

Вызов refresh() отображает участок площадки в прямоугольнике, простирающийся от координаты (5,5) до координаты (20,75) на экране; верхний левый угол отображаемого участка имеет координату (0,0) на площадке. Помимо этого различия, прокладки точно такие же, как обычные окна, и поддерживают один и тот же методы.

Если у вас несколько окон и площадок на экране есть более эффективный способ обновить экран и предотвратить раздражающее мерцание экрана по мере обновления каждой части экрана. refresh() на самом деле делает две вещи:

  1. Вызов noutrefresh() метод каждого окна для обновления базовой структуры данных, представляющей требуемую состояние экрана.
  2. Вызывает функцию doupdate(), которая изменить физический экран, чтобы соответствовать желаемому состояние, зарегистрированному в структуре данных.

Вместо этого вы можете назвать noutrefresh() в ряде окон, чтобы обновить структуру данных и затем назвать doupdate(), чтобы обновить экран.

Отображение текста

С точки зрения C-программиста, curses иногда может выглядеть как крутой лабиринт функций, все тонко отличаются. Для примера,:c:func: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 строка или bytestring как стоимость, которая будет показана. Содержимое байт-строк отправляется на терминал как есть. Строки кодированный байтам с использованием значения атрибута encoding окна; по умолчанию используется система кодировка по умолчанию, возвращаемая locale.getpreferredencoding().

addch() методы принимают символ, который может быть строка длины 1, байтестрингом длины 1 или целым числом.

Константы предоставляются для символов расширения; эти константы являются целыми числами больше 255. Например, ACS_PLMINUS является символом +/-, ACS_ULCORNER верхним левым углом рамки (удобным для построения границ). Вы можете также использовать соответствующий Unicode символ.

Windows помнит, где курсор был оставлен после последней операции, поэтому, если оставить координаты y,x, строка или символ будут отображаться везде, где последняя операция остановлен. Можно также переместить курсор с помощью move(y,x) метод. Поскольку некоторые клеммы всегда отображают мигающий курсор, может понадобиться убедиться, что курсор расположен в каком-то месте, где он не будет отвлекать; может быть запутанным, когда курсор мигает в каком-то явно случайном месте.

Если приложение вообще не нуждается в мигающем курсоре, можно вызвать функцию curs_set(False), чтобы сделать его невидимым. Для совместимости со старыми версиями curses существует функция leaveok(bool), которая является синонимом curs_set(). Когда bool будет верен, библиотека curses попытается подавить вспыхивающий курсор, и вы не должны будете волноваться об отъезде его в странных местоположениях.

Атрибуты и цвет

Символы могут отображаться различными способами. Строки состояния в текстовом приложении обычно отображаются в обратном видео, или для просмотра текста может потребоваться выделить определенные слова. 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, за которой следуют цветовые xterms.

Для использования цвета необходимо вызвать функцию start_color() вскоре после вызова функции initscr(), чтобы инициализировать набор цветов по умолчанию (функция curses.wrapper() делает это автоматически). После этого функция has_colors() возвращает TRUE, если используемый терминал действительно может отображать цвет. (Примечание: curses использует американское написание «color» вместо канадского/британского написания «color». Если вы - используемый к британскому правописанию, вы должны будете подчиниться орфографической ошибке его ради этих функций.)

Библиотека curses поддерживает конечное число пар цветов, содержащих цвет переднего плана (или текста) и цвет фона. Можно получить значение атрибут, соответствующее цветовой паре с функцией color_pair(); это может быть побитово иЛИ с другими атрибуты, такими как A_REVERSE, но опять же, такие комбинации не гарантированно работают на всех терминалах.

Пример, в котором отображается строка текста с использованием пары цветов 1:

stdscr.addstr("Pretty text", curses.color_pair(1))
stdscr.refresh()

Как я уже говорил, цветовая пара состоит из цвета переднего плана и фона. Функция init_pair(n, f, b) изменяет определение пары цветов n на цвет переднего плана f и цвет фона b. Пара цветов 0 жестко подключена к белому на черном и не может быть изменена.

Цвета нумеруются, и start_color() инициализирует 8 основных цветов при активации цветового режима. Они: 0: чёрные, 1: красные, 2: зелёные, 3: жёлтые, 4: синие, 5: пурпурные, 6: голубые и 7: белые. Модуль curses определяет именованные константы для каждого из этих цветов: curses.COLOR_BLACK, curses.COLOR_RED и т.д.

Давайте соберем все вместе. Чтобы изменить цвет 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, если способность там. Если вам посчастливилось иметь такой талантливый терминал, обратитесь к справочным страницам вашей системы для получения дополнительной информации.

Пользовательский ввод

Библиотека C curses предлагает только очень простые входные механизмы. Модуль Python’s curses добавляет базовый виджет ввода текста. (Другие библиотеки такие как Urwid содержат более обширные коллекции виджетов.)

Есть два методы для того, чтобы быть введенными от window:

  • getch() освежает экран и затем ждет, чтобы пользователь поразил ключ, показав ключ, если echo() назвали ранее. Можно дополнительно указать координату, на которую следует переместить курсор перед приостановкой.
  • getkey() делает то же самое, но преобразует целое число в строка. Отдельные знаки возвращены как 1-символ последовательности, и специальные ключи, такие как функциональные клавиши возвращают дольше строки, содержащий ключевое имя, такое как KEY_UP или ^G.

Можно не ждать пользователя с помощью окна nodelay() метод. После nodelay(True) getch() и getkey() для окна становятся неблокирующими. Для сигнализации о том, что вход не готов, getch() возвращает curses.ERR (значение -1) и getkey() вызывает исключение. Существует также функция 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  # Exit the while loop
    elif c == curses.KEY_HOME:
        x = y = 0

Модуль curses.ascii поставляет функции членства ASCII класс, которые берут или целое число или 1-символ аргументы строка; они могут быть полезны при написании более читаемых тестов для таких циклов. Он также предоставляет функции преобразования, которые принимают целочисленные или 1-character-строка аргументы и возвращают один и тот же тип. Например, curses.ascii.ctrl() возвращает управляющий символ, соответствующий его аргументу.

Есть также метод, чтобы извлечь целую строку, getstr(). Это не используемый очень часто, потому что его функциональность вполне ограничена; единственными доступными клавишами редактирования являются клавиша backspace и клавиша Enter, которая завершает работу строка. Она может быть дополнительно ограничена фиксированным количеством символов.:

curses.echo()            # Enable echoing of characters

# Get a 15-character string, with the cursor on the top line
s = stdscr.getstr(0,0, 15)

Модуль curses.textpad предоставляет текстовое поле, поддерживающее набор клавиш типа Emacs. Различный методы редактирования поддержки Textbox класс с входной проверкой и сбором отредактировать результаты или с или не таща места. Вот пример:

import curses
from curses.textpad import Textbox, rectangle

def main(stdscr):
    stdscr.addstr(0, 0, "Enter IM message: (hit Ctrl-G to send)")

    editwin = curses.newwin(5,30, 2,1)
    rectangle(stdscr, 1,0, 1+5+1, 1+30+1)
    stdscr.refresh()

    box = Textbox(editwin)

    # Let the user edit until Ctrl-G is struck.
    box.edit()

    # Get resulting contents
    message = box.gather()

См. документацию библиотеки относительно curses.textpad для получения дополнительной информации.

Для получения дополнительной информации

Этот HOWTO не охватывает некоторые расширенные темы, такие как чтение содержимого экрана или захват событий мыши из экземпляра xterm, но страница библиотеки Python для модуля curses теперь достаточно завершена. Вы должны просмотреть его дальше.

Если вы сомневаетесь в подробном поведении функций curses, обратитесь к страницам руководства для вашей реализации curses, будь то ncurses или собственный поставщик Unix. Страницы руководства задокументируют любые причуды и предоставят полный список всех доступных вам функций, атрибутов и символов ACS_*.

Поскольку API curses настолько велик, некоторые функции не поддерживаются в интерфейсе Python. Часто это происходит не потому, что их трудно реализовать, а потому, что они еще никому не нужны. Кроме того, Python еще не поддерживает библиотеку меню, связанную с ncurses. Исправления, добавляющие поддержку для них, будут приветствоваться; см. Руководство разработчика Python подробнее о отправке исправлений в Python.