Программирование на 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. Сторонний пакет windows-curses предоставляет тот же интерфейс в Windows.
Модуль curses в Python
Модуль Python — это довольно простая обёртка над функциями C, предоставляемыми curses; если вы уже знакомы с программированием на C с помощью curses, перенести эти знания на Python будет очень просто. Главное отличие состоит в том, что интерфейс Python упрощает работу, объединяя разные функции C, такие как addstr(), mvaddstr() и mvwaddstr(), в один метод addstr(). Подробнее об этом будет рассказано далее.
Это руководство знакомит с созданием текстовых программ на curses и Python. Оно не претендует на роль полного руководства по API curses; для этого обратитесь к разделу о ncurses в руководстве по библиотеке Python и к страницам руководства C по ncurses. Однако здесь изложены основные идеи.
Запуск и завершение приложения curses
Прежде чем что-либо делать, 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(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()выполняет то же самое, но преобразует целое число в строку. Обычные символы возвращаются в виде строк длины 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 и возвращающие значение того же типа. Например, 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 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.14/howto/curses.html