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