Spec-Zone.ru › Python 3.9

Программирование с использованием Curses в Python

Автор

A.M. Kuchling, Eric S. Raymond

Версия

2.04

Аннотация

В данном документе описывается, как использовать расширение модуля curses для управления текстовыми дисплеями.

Что такое curses?

Библиотека curses предоставляет независимую от терминала функцию отрисовки экрана и обработки ввода с клавиатуры для текстовых терминалов; такие терминалы включают VT100, консоль Linux и имитируемый терминал, предоставляемый различными программами. Дисплейные терминалы поддерживают различные управляющие коды для выполнения общих операций, таких как перемещение курсора, прокрутка экрана и стирание областей. Разные терминалы используют совершенно разные коды и часто имеют свои особенности.

В мире графических дисплеев можно задаться вопросом: «Зачем это нужно?». Действительно, терминалы с отображением символов на ячейках — устаревшая технология, но в некоторых нишах их функциональность всё ещё полезна. Одна из таких ниш — небольшие или встроенные Unix-системы, на которых не работает сервер X. Другая — инструменты, такие как установщики ОС и конфигураторы ядра, которые могут запускаться до появления графической поддержки.

Библиотека curses предоставляет достаточно базовые функции, предоставляя программисту абстракцию дисплея, содержащего несколько неперекрывающихся текстовых окон. Содержимое окна можно изменять различными способами — добавлять текст, стирать его, изменять его внешний вид — и библиотека curses выяснит, какие управляющие коды необходимо отправить терминалу, чтобы получить нужный результат. Библиотека curses не предоставляет многие концепции пользовательского интерфейса, такие как кнопки, флажки или диалоговые окна; если вам нужны такие функции, рассмотрите библиотеку пользовательского интерфейса, такую как Urwid.

Библиотека curses была первоначально написана для BSD Unix; более поздние версии Unix System V от AT&T добавили множество улучшений и новых функций. BSD curses больше не поддерживается, её заменил ncurses — это открытый исходный код реализации интерфейса AT&T. Если вы используете систему с открытым исходным кодом, такую как Linux или FreeBSD, ваша система почти наверняка использует ncurses. Поскольку большинство современных коммерческих версий Unix основаны на коде System V, все описанные здесь функции, вероятно, будут доступны. Однако более старые версии curses, доступные в некоторых проприетарных Unixes, могут не поддерживать всё.

Версия Python для Windows не включает модуль curses. Доступна переносимая версия под названием UniCurses.

Модуль 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() на самом деле выполняет две вещи:

  1. Вызывает метод noutrefresh() каждого окна для обновления внутренней структуры данных, представляющей желаемое состояние экрана.
  2. Вызывает функцию 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 не гарантирует, что все возможные комбинации доступны или что все они визуально различаются. Это зависит от возможностей используемого терминала, поэтому безопаснее придерживаться наиболее распространенных доступных атрибутов, перечисленных здесь.

Атрибут

Описание

A_BLINK

Мигающий текст

A_BOLD

Очень яркий или полужирный текст

A_DIM

Текст с пониженной яркостью

A_REVERSE

Текст в инверсном режиме

A_STANDOUT

Лучший доступный режим выделения

A_UNDERLINE

Подчеркнутый текст

Итак, чтобы отобразить строку состояния в инверсном режиме в верхней строке экрана, вы можете написать:

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.9/howto/curses.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API