Spec-Zone.ru › Python 3.10

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

Автор

А.М. Кучлинг, Эрик С. Реймонд

Версия

2.04

Аннотация

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

Что такое 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-системами, могут не поддерживать всё.

Windows-версия Python не включает модуль 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) — это специальный случай окна; она может быть больше, чем фактический дисплей, и отображается только часть области за раз. Для создания области требуется высота и ширина области, а для обновления области — координаты области на экране, где будет отображаться часть области.

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(), принимают несколько форм аргументов. Обычно существует четыре различных формы.

Форма

Описание

строка или символ

Отобразить строку строка или символ символ в текущей позиции

строка или символ, атрибут

Отобразить строку строка или символ символ, используя атрибут атрибут в текущей позиции

y, x, строка или символ

Переместиться в позицию y,x в окне и отобразить строка или символ

y, x, строка или символ, атрибут

Переместиться в позицию y,x в окне и отобразить строка или символ, используя атрибут атрибут

Атрибуты позволяют отображать текст в выделенных форматах, таких как полужирный, подчеркнутый, инверсный или цветной. Они будут подробно объяснены в следующем подразделе.

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

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

Библиотека Curses предлагает только очень простые механизмы ввода. Модуль Python 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.

Дополнительная информация

В данном руководстве не рассматриваются некоторые сложные темы, такие как чтение содержимого экрана или захват событий мыши из экземпляра 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.10/howto/curses.html

Spec-Zone.ru

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