Программирование на Python с помощью curses
- Автор:
-
A.M. Kuchling, Eric S. Raymond
- Выпуск:
-
2.04
Что такое curses?
Библиотека curses предоставляет независимые от типа терминала средства отображения на экране и обработки клавиатуры для текстовых терминалов; к ним относятся VT100, консоль Linux и эмулируемые терминалы, предоставляемые различными программами. Дисплейные терминалы поддерживают различные управляющие коды для выполнения распространённых операций, таких как перемещение курсора, прокрутка экрана и стирание областей. Разные терминалы используют совершенно разные коды и зачастую имеют собственные небольшие особенности.
В мире графических дисплеев можно спросить: «Зачем беспокоиться?» Действительно, символьные дисплейные терминалы — устаревшая технология, но есть области, в которых возможность выполнять с их помощью сложные операции всё ещё ценна. Одна из таких областей — компактные или встраиваемые системы Unix, на которых не запущен X-сервер. Другая — такие инструменты, как программы установки ОС и настройки ядра, которым, возможно, придётся работать до появления поддержки графики.
Библиотека curses предоставляет довольно базовую функциональность: программисту доступна абстракция дисплея, состоящего из нескольких неперекрывающихся текстовых окон. Содержимое окна можно изменять разными способами — добавлять текст, стирать его, менять его внешний вид, — а библиотека curses определит, какие управляющие коды нужно отправить терминалу, чтобы получить нужный результат. В curses нет многих понятий пользовательского интерфейса, таких как кнопки, флажки или диалоговые окна; если вам нужны такие возможности, рассмотрите библиотеку пользовательского интерфейса, например Urwid.
Библиотека curses изначально была написана для BSD Unix; в более поздние версии Unix от AT&T на основе System V добавили множество улучшений и новых функций. Поддержка BSD curses прекращена: её заменила ncurses — реализация интерфейса AT&T с открытым исходным кодом. Если вы используете Unix с открытым исходным кодом, например Linux или FreeBSD, в вашей системе почти наверняка используется ncurses. Поскольку большинство современных коммерческих версий Unix основаны на коде System V, вероятно, будут доступны все описанные здесь функции. Однако более старые версии curses, поставляемые с некоторыми проприетарными Unix, могут поддерживать не всё.
Версия Python для Windows не включает модуль curses. Доступна его перенесённая версия под названием UniCurses.
Модуль curses в Python
Модуль Python — это довольно простой интерфейс к функциям C, предоставляемым curses; если вы уже знакомы с программированием на C с использованием curses, перенести эти знания на Python будет очень просто. Главное отличие заключается в том, что интерфейс Python упрощает работу, объединяя различные функции C, такие как addstr(), mvaddstr() и mvwaddstr(), в один метод addstr(). Позже мы рассмотрим это подробнее.
Это руководство знакомит с написанием программ в текстовом режиме с использованием curses и Python. Оно не претендует на роль полного руководства по API curses; для этого обратитесь к разделу о ncurses в справочнике по библиотеке Python и к страницам руководства по ncurses для C. Однако здесь будут изложены основные идеи.
Запуск и завершение приложения 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, нужно включить режим keypad.
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() восстановит исходное состояние терминала. Вызываемый объект запускается внутри конструкции try…except, которая перехватывает исключения, восстанавливает состояние терминала, а затем повторно вызывает исключение. Таким образом, при возникновении исключения терминал не останется в странном состоянии, и вы сможете прочитать сообщение об исключении и трассировку стека.
Окна и панели
Окна — основная абстракция в 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. Большинство программ выполняют множество операций, а затем приостанавливаются в ожидании нажатия клавиши или другого действия пользователя. Достаточно убедиться, что экран перерисован перед ожиданием пользовательского ввода: для этого сначала вызовите 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() выполняет два действия:
- Вызывает метод
noutrefresh()каждого окна для обновления внутренней структуры данных, представляющей желаемое состояние экрана. - Вызывает функцию
doupdate(), чтобы привести физический экран в соответствие с желаемым состоянием, записанным в структуре данных.
Вместо этого можно вызвать noutrefresh() для нескольких окон, чтобы обновить структуру данных, а затем вызвать doupdate() для обновления экрана.
Отображение текста
С точки зрения программиста на 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 в качестве отображаемого значения. Содержимое байтовых строк отправляется в терминал без изменений. Строки кодируются в байты с использованием значения атрибута окна encoding; по умолчанию используется системная кодировка, возвращаемая функцией locale.getencoding().
Методы addch() принимают символ, которым может быть строка длиной 1, байтовая строка длиной 1 или целое число.
Для дополнительных символов предусмотрены константы; их значения — целые числа больше 255. Например, ACS_PLMINUS — это символ +/−, а ACS_ULCORNER — верхний левый угол рамки (удобно для рисования границ). Также можно использовать соответствующий символ Unicode.
Окна запоминают положение курсора после последней операции, поэтому, если не указывать координаты y,x, строка или символ будут отображены там, где завершилась предыдущая операция. Можно также переместить курсор методом move(y,x). Поскольку некоторые терминалы всегда отображают мигающий курсор, возможно, стоит располагать его там, где он не будет отвлекать; курсор, мигающий в неожиданном месте, может сбивать с толку.
Если приложению мигающий курсор вообще не нужен, можно вызвать curs_set(False), чтобы сделать его невидимым. Для совместимости со старыми версиями curses существует функция leaveok(bool), являющаяся синонимом curs_set(). Если значение bool равно true, библиотека curses попытается подавить мигание курсора, и вам не придётся беспокоиться о том, что он останется в неподходящем месте.
Атрибуты и цвет
Символы можно отображать по-разному. Строки состояния в текстовых приложениях обычно показываются инверсным видео, а в программе для просмотра текста может потребоваться выделять определённые слова. curses поддерживает это, позволяя указать атрибут для каждой ячейки экрана.
Атрибут — это целое число, каждый бит которого соответствует отдельному атрибуту. Можно попробовать отобразить текст с несколькими установленными битами атрибутов, но curses не гарантирует, что доступны все возможные комбинации или что они будут визуально различаться. Это зависит от возможностей используемого терминала, поэтому безопаснее ограничиться наиболее распространёнными атрибутами, перечисленными здесь.
Атрибут | Описание |
|---|---|
Мигающий текст | |
Яркий или полужирный текст | |
Тусклый текст | |
Текст с инверсией видео | |
Наилучший из доступных режимов выделения | |
Подчёркнутый текст |
Например, чтобы отобразить строку состояния с инверсией видео в верхней строке экрана, можно написать:
stdscr.addstr(0, 0, "Current mode: Typing mode",
curses.A_REVERSE)
stdscr.refresh()
Библиотека curses также поддерживает цвет на терминалах, в которых он доступен. Самый распространённый такой терминал — вероятно, консоль Linux, за которой следуют цветные xterm.
Чтобы использовать цвет, вскоре после вызова initscr() необходимо вызвать функцию start_color() для инициализации набора цветов по умолчанию (функция 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, если такая возможность имеется. Если вам повезло и у вас настолько функциональный терминал, обратитесь к страницам руководства вашей системы за дополнительной информацией.
Пользовательский ввод
Библиотека C curses предоставляет лишь очень простые средства ввода. Модуль 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 # Exit the while loop
elif c == curses.KEY_HOME:
x = y = 0
Модуль curses.ascii предоставляет функции проверки принадлежности символов к классам ASCII, принимающие целые числа или строки длиной в один символ; они могут помочь сделать проверки в таких циклах более понятными. Кроме того, он предоставляет функции преобразования, которые принимают целые числа или строки длиной в один символ и возвращают значение того же типа. Например, 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.
Дополнительная информация
В этом руководстве не рассматриваются некоторые сложные темы, например чтение содержимого экрана или обработка событий мыши из экземпляра xterm, однако страница библиотеки Python, посвящённая модулю curses, теперь достаточно полна. Рекомендуем обратиться к ней.
Если у вас возникли вопросы о подробном поведении функций curses, обратитесь к страницам руководства по используемой реализации curses — ncurses или проприетарной версии от поставщика Unix. В этих страницах описаны особенности реализации, а также приведены полные списки всех доступных функций, атрибутов и символов ACS_*.
Из-за большого размера API curses некоторые функции не поддерживаются интерфейсом Python. Часто это не связано со сложностью их реализации — просто они пока никому не понадобились. Кроме того, Python пока не поддерживает библиотеку меню, связанную с ncurses. Будем рады исправлениям, добавляющим такую поддержку; сведения о том, как отправлять исправления для Python, см. в руководстве разработчика Python.
- Программирование с NCURSES: подробное руководство для программистов на C.
- Страница руководства ncurses
- Часто задаваемые вопросы о ncurses
- «Используйте curses… не ругайтесь»: видеозапись доклада PyCon 2013 об управлении терминалами с помощью curses или Urwid.
- «Консольные приложения с Urwid»: видеозапись доклада PyCon CA 2012 с демонстрацией нескольких приложений, написанных с использованием Urwid.
© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/howto/curses.html