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

Программирование с curses на PythonCurses Programming with Python

Автор:A.M. Kuchling, Eric S. Raymond
Версия:2.04

Аннотация

Этот документ описывает, как использовать curses модуль расширения для управления текстовыми дисплеями.

Что такое curses?What is curses?

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

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

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

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

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

Модуль 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), в виде многобайтовых 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):
    # Очистить экран
    stdscr.clear()

    # Это вызывает ZeroDivisionError, когда 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() восстанавливает исходное состояние терминала. Вызываемый объект выполняется внутри конструкции try...except, которая перехватывает исключения, восстанавливает состояние терминала и затем повторно возбуждает исключение. Поэтому ваш терминал не останется в странном состоянии при исключении, и вы сможете прочитать сообщение об исключении и traceback.

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

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

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

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

Pad – это частный случай окна; он может быть больше фактического экрана, и единовременно отображается только часть pad’а. Для создания pad’а требуются его высота и ширина, а для обновления pad’а необходимо указать координаты области на экране, в которой будет отображаться часть pad’а.

pad = curses.newpad(100, 100)
# Эти циклы заполняют pad буквами; addch() описана
# в следующем разделе
for y in range(0, 99):
    for x in range(0, 99):
        pad.addch(y,x, ord('a') + (x*x+y*y) % 26)

# Отображает участок pad в центре экрана.
# (0,0) : координата верхнего левого угла отображаемой области pad.
# (5,5) : координата верхнего левого угла области окна, которая будет заполнена
#         содержимым pad.
# (20, 75) : координата нижнего правого угла области окна, которая будет
#          : заполнена содержимым pad.
pad.refresh( 0,0, 5,5, 20,75)

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

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

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

Вместо этого можно вызвать noutrefresh() для нескольких окон, чтобы обновить структуру данных, а затем вызвать 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 или bytestring в качестве отображаемого значения. Содержимое bytestring'ов отправляется на терминал как есть. Строки кодируются в байты с использованием значения атрибута encoding окна; по умолчанию используется системная кодировка, возвращаемая locale.getpreferredencoding().

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

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

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

Если вашему приложению вообще не нужен мигающий курсор, можно вызвать curs_set(False), чтобы сделать его невидимым. Для совместимости со старыми версиями curses существует функция leaveok(bool), которая является синонимом curs_set(). Когда 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() делает это автоматически). После этого функция has_colors() возвращает TRUE, если используемый терминал действительно может отображать цвет. (Примечание: в curses используется американское написание «color» вместо канадско-британского «colour». Если вы привыкли к британскому написанию, придётся смириться с его искажением ради этих функций.)

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

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

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

Существует два метода получения ввода из окна:

  • getch() обновляет экран, а затем ожидает нажатия клавиши пользователем, отображая её, если ранее была вызвана функция echo(). При желании можно указать координаты, на которые следует переместить курсор перед ожиданием.
  • getkey() делает то же самое, но преобразует целое число в строку. Отдельные символы возвращаются в виде строк из одного символа, а специальные клавиши, такие как функциональные, возвращают более длинные строки, содержащие имя клавиши, например 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  # Выйти из цикла 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)

Модуль 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)

    # Дать пользователю редактировать, пока не будет нажато Ctrl-G.
    box.edit()

    # Получить полученное содержимое
    message = box.gather()

За дополнительными сведениями обратитесь к документации по модулю curses.textpad.

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

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

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

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