Программирование на curses с помощью Python
- Автор:
-
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; если вы уже знакомы с программированием на curses на C, перенести эти знания в 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 или строку байтов для отображения. Содержимое строк байтов отправляется терминалу без изменений. Строки 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–2025 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/howto/curses.html