Программирование с помощью Curses в Python
- Автор
-
А.М. Кучлинг, Эрик С. Реймонд
- Версия
-
2.04
Аннотация
Этот документ описывает, как использовать модуль расширения curses, чтобы управлять текстовыми отображениями.
Что такое curses?
Библиотека curses предоставляет терминально-независимые средства для рисования на экране и обработки ввода с клавиатуры для текстовых терминалов; такие терминалы включают VT100, консоль Linux и симулированный терминал, предоставляемый различными программами. Дисплейные терминалы поддерживают различные управляющие коды для выполнения общих операций, таких как перемещение курсора, прокрутка экрана и стирание областей. Разные терминалы используют сильно отличающиеся коды и часто имеют свои особенности.
В мире графических дисплеев можно спросить: «Зачем это нужно?» Действительно, терминалы с отображением символов на ячейках — устаревшая технология, но существуют ниши, в которых умение выполнять сложные операции с ними всё ещё ценно. Одна из таких ниш — небольшие или встраиваемые Unix-системы, на которых не запущен сервер X. Другая — инструменты, такие как установщики ОС и конфигураторы ядра, которые могут потребоваться запустить до появления графической поддержки.
Библиотека curses предоставляет довольно базовые функции, предоставляя программисту абстракцию дисплея, содержащего несколько непересекающихся текстовых окон. Содержимое окна можно изменять различными способами — добавление текста, его удаление, изменение внешнего вида — и библиотека curses выяснит, какие управляющие коды необходимо отправить терминалу, чтобы получить нужный результат. Библиотека curses не предоставляет много концепций пользовательского интерфейса, таких как кнопки, флажки или диалоговые окна; если вам нужны такие функции, рассмотрите библиотеку пользовательского интерфейса, такую как Urwid.
Библиотека curses первоначально была написана для BSD Unix; более поздние версии Unix от AT&T добавили множество улучшений и новых функций. BSD curses больше не поддерживается, её заменил ncurses — это открытая реализация интерфейса AT&T. Если вы используете открытую Unix-систему, такую как Linux или FreeBSD, ваша система, скорее всего, использует ncurses. Поскольку большинство современных коммерческих версий Unix основаны на коде System V, все описанные здесь функции, вероятно, будут доступны. Однако более ранние версии curses, предоставляемые некоторыми проприетарными Unix-системами, могут не поддерживать всё.
Версия Python для Windows не включает модуль curses. Доступна портированная версия под названием UniCurses. Вы также можете попробовать модуль Console, написанный Фредриком Лундхом, который не использует тот же API, что и curses, но предоставляет вывод адресовываемого курсором текста и полную поддержку ввода с мыши и клавиатуры.
Модуль Python curses
Модуль Python представляет собой довольно простой обертку над C-функциями, предоставляемыми curses; если вы уже знакомы с программированием с помощью curses на C, перенести эти знания на Python очень легко. Главное отличие заключается в том, что интерфейс Python упрощает вещи, объединяя различные C-функции, такие как addstr(), mvaddstr(), и mvwaddstr() в один метод addstr(). Вы увидите это более подробно позже.
Данное руководство является введением в написание программ в текстовом режиме с использованием 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 выполнить эту работу, необходимо включить режим 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 перерисовать окно не сильно усложняет программирование с 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.getpreferredencoding().
Методы 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.
Для использования цвета необходимо вызвать функцию 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, если такая возможность существует. Если вам посчастливилось иметь такой способный терминал, обратитесь к страницам руководства вашей системы для получения дополнительной информации.
Ввод пользователя
Библиотека C curses предоставляет только очень простые механизмы ввода. Модуль Python’s curses добавляет виджет базового текстового ввода. (Другие библиотеки, такие как 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.
Дополнительная информация
Данный HOWTO не охватывает некоторые продвинутые темы, такие как чтение содержимого экрана или захват событий мыши из экземпляра xterm, но страница Python-библиотеки для модуля curses сейчас достаточно полная. Вам следует изучить её дальше.
Если у вас есть сомнения по поводу подробного поведения функций curses, обратитесь к страницам руководства для вашей реализации curses, будь то ncurses или проприетарная реализация Unix. Страницы руководства задокументируют любые особенности и предоставят полные списки всех функций, атрибутов и ACS_* символов, доступных вам.
Поскольку API curses очень обширен, некоторые функции не поддерживаются в интерфейсе Python. Часто это не связано с трудностями реализации, а с тем, что пока никто в них не нуждался. Кроме того, Python ещё не поддерживает библиотеку меню, связанную с ncurses. Исправления, добавляющие поддержку этих возможностей, приветствуются; см. Руководство разработчика Python, чтобы узнать больше о представлении исправлений для Python.
- Writing Programs with NCURSES: подробный учебник для программистов на C.
- Страница руководства ncurses
- FAQ ncurses
- “Use curses… don’t swear”: видео с выступления на PyCon 2013 о управлении терминалами с использованием curses или Urwid.
- “Console Applications with Urwid”: видео с выступления на PyCon CA 2012, демонстрирующее некоторые приложения, написанные с использованием Urwid.
© 2001–2022 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.8/howto/curses.html