Spec-Zone.ru › Python 3.12

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

Автор:

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

Версия:

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

Форма

Описание

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 — верхний левый угол рамки (полезно для рисования границ). Вы также можете использовать соответствующий символ Юникода.

Окна запоминают позицию курсора после последней операции, поэтому если вы опустите координаты 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’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–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/howto/curses.html

Spec-Zone.ru

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