Spec-Zone.ru › Python 3.11

Программирование с использованием 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.

Модуль 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()

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

Атрибут

Описание

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, если эта возможность доступна. Если вам повезёт, и у вас есть такой талантливый терминал, обратитесь к страницам справки вашей системы для получения дополнительной информации.

Ввод пользователя

Библиотека Curses предлагает только очень простые механизмы ввода. Модуль Python’s 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–2023 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.11/howto/curses.html

Spec-Zone.ru

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