Программирование PC Python: программирование на curses Sat, March 29 2025  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.


Python: программирование на curses Печать
Добавил(а) microsin   

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

В мире, где повсеместное распространение получили графические дисплеи можно было бы задаться вопросам "а стоит или беспокоиться"? И правда, терминалы с символьным выводом - устаревшая технология, однако есть ниши, в которых остается важной их продвинутое использование. Одна их таких ниш - компактные, или встраиваемые операционные системы Linux, где нельзя запустить сервер X. Другой пример - утилиты инсталляторов ОС и конфигураторы ядра, которые должны запускаться в среде, где пока недоступна поддержка графики.

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

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

Windows-версия пакета Python не включает в себя модуль curses. Доступна портированная версия UniCurses и устанавливаемый модуль windows-curses, доступный для инсталляции через pip3 [2].

Модуль Python curses это довольно простая обертка над C-функциями curses. Если вы знакомы с программированием curses на C, то довольно быстро перенесете свои знания на Python. Самое большое отличие заключается в том, что интерфейс Python упрощает работу, объединяя различные C-функции, такие как addstr(), mvaddstr() и mvwaddstr() в один метод addstr(). Более подробно это будет показано дальше.

Эта документация HOWTO (перевод [1]) может послужить введением в создание программ текстового режима на основе curses и Python. Не делается попытка обеспечить полное руководство по curses API; для этого см. описание библиотеки Python ncurses, и страницы C-документации ncurses. Однако здесь вы познакомитесь с базовыми концепциями использования curses.

[Начало и завершение приложения curses]

initscr(). Перед тем, чтобы что-то делать, библиотека curses должна быть инициализирована. Это делается вызовом функции initscr(), которая определит тип терминала, пошлет в терминал любые необходимые коды настройки, и создаст различные внутренние структуры данных. В случае успеха initscr() возвратит объект окна, представляющего весь экран; это обычно называется stdscr после имени соответствующей переменной C.

import curses
stdscr = curses.initscr()

noecho(), cbreak(). Обычно приложения на основе 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()

Это вернет обратно настройки терминала. Затем вызовите функцию endwin(), чтобы восстановить оригинальный рабочий режим терминала.

curses.endwin()

curses.wrapper. Общая проблема при отладке приложения curses - потеря вашего терминала, когда приложение упало без восстановления терминала в предыдущее состояние. На Python это обычно происходит, когда ваш код содержит баги, и вызывает не перехваченное исключение. Например, нажатия на клавиши больше не появляются в терминале, что усложняет использование терминала.

В Python вы можете избежать этих сложностей и упростить отладку с помощью импорта функции curses.wrapper(), и использования её следующим образом:

from curses import wrapper

def main(stdscr): # Очистка экрана: stdscr.clear()
# Это вызовет исключение ZeroDivisionError, когда 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)

Популярен также и такой способ оформления программы:

def main():
    curses.wrapper(my_func)

if __name__ == "__main__": main()

Функция wrapper() принимает вызываемый объект и делает описанные выше инициализации, также инициализируя поддержку цветов, если она присутствует. Затем wrapper() запустит предоставленную вами функцию (в этом примере main, или my_func). Как только произойдет возврат из этой функции, wrapper() восстановит оригинальное состояние терминала. Вызываемая функция запускается внутри блока try ... except, который перехватывает исключения, восстанавливает состояние терминала, и затем повторно вызывает исключение (чтобы диагностическое сообщение об ошибке было отображено на экране традиционным образом). Таким образом, ваш терминал вернется при падении программы в рабочее состояние, и вы можете ознакомиться с сообщением об ошибке исключения и информацией обратной трассировки вызовов функций.

[Window и Pad]

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

Pad. К специальному виду окна относится pad (можно перевести как "площадка", или "блокнот"). Pad может быть больше, чем фактический экран дисплея, и в любой момент времени будет отображаться только часть содержимого pad. Создание pad требует указания высоты (height) и ширины (width), в то время как обновление pad на экране требует предоставления координат области, где будет отображаться субсекция pad.

pad = curses.newpad(100, 100)

# Эти циклы заполнят pad буквами; метод addch() будет
# описываться далее.
for y in range(0, 99): for x in range(0, 99): pad.addch(y,x, ord('a') + (x*x+y*y) % 26)

# Отобразит секцию pad посередине экрана.
# (0,0): координата верхнего левого угла отображаемой секции pad.
# (5,5): координата верхнего левого угла области окна, заполняемой
# содержимым pad.
# (20, 75): координата нижнего правого угла области окна, заполняемого
# содержимым pad. pad.refresh( 0,0, 5,5, 20,75)

Вызов refresh() отобразит на экране секцию pad в прямоугольнике, полученном из координат между (5,5) и (20,75); верхний левый угол отображаемой секции это координата (0,0) на pad. Помимо этого отличия, pad-ы это обычные окна, поддерживающие те же самые методы.

Если у вас на экране есть несколько окон и pad-ов, существует более эффективный способ обновить экран и избежать нежелательных мерцаний экрана. Метод refresh() фактически делает две вещи:

1. Вызывает метод noutrefresh() на каждом окне, чтобы обновить структуру нижележащих данных, представляющих желаемое состояние экрана.

2. Вызывает функцию doupdate(), чтобы поменять физический экран, после чего он будет соответствовать желаемому состоянию, записанному в структуре данных.

Вместо этого вы можете вызывать noutrefresh() на нескольких окнах, чтобы обновить структуру данных, и затем вызвать doupdate() для обновления экрана.

[Отображение текста]

С точки зрения программиста на языке C, библиотека curses может иногда выглядеть как извилистый лабиринт функций, все они немного отличаются. Например, addstr() отобразит строку на окне stdscr в текущей позиции курсора, в то время как mvaddstr() сначала переместится к предоставленной координате y,x перед отображением текста. Метод waddstr() почти то же самое, что и addstr(), однако указывает используемое окно вместо использования stdscr по умолчанию. Метод mvwaddstr() позволяет указать и окно, и координату вывода текста.

К счастью, интерфейс Python скрывает все эти детали. Т. е. stdscr это объект окна, как и любой другой объект окна, а методы, такие как addstr(), принимают формы с различным количеством аргументов. Обычно существуют 4 разные формы.

Форма Описание
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 или строку байт (bytestring) в качестве значения для отображения. Содержимое байтовой строки посылается в терминал как есть, без перекодировки. Строки перекодируются в bytestring с использованием значения атрибута кодировки текста окна; по умолчанию используется системная кодировка по умолчанию, возвращаемая locale.getencoding().

Метод addch() принимает символ, который может быть либо строкой длиной 1, либо bytestring длиной 1, либо целым числом (integer).

Для символов расширения предусмотрены константы; эти константы представляют целые числа со значениями больше 255. Например, ACS_PLMINUS это символ +/-, а ACS_ULCORNER это верхний левый угол ящика (удобно для рисования бордюров окна). Вы можете также использовать подходящий символ Unicode.

Окна curses запоминают позицию курсора, в которой он был оставлен после последней операции с окном; поэтому если вы опустите координаты y,x, то строка или символ будут отображены там, где была закончена последняя операция. Вы также можете переместить курсор методом move(y,x). Из-за того, что некоторые терминалы всегда отображают мигающий курсор, вы можете захотеть, чтобы курсор был позиционирован в какое-нибудь место, где бы он не отвлекал; может сбить с толку мигание курсора в каком-то, по-видимому, случайном месте.

Если вашему приложению вообще не нужен мигающий курсор, то вы можете вызвать curs_set(False), чтобы сделать его невидимым. Для совместимости со старыми версиями curses имеется функция leaveok(bool), которая является синонимом curs_set(). Когда bool имеет значение true, библиотека curses попытается подавить мигающий курсор, и вам не нужно беспокоиться о том, что курсор окажется в странных местах.

Атрибуты и цвет. Символы могут быть отображены разными способами. Строки статуса в приложении, основанном на тексте, обычно отображаются с инверсией (хороший пример - утилита текстового редактора nano), или просмотрщик текста может потребовать подсветку определенных слов (часто используется в утилите просмотра документации man). Библиотека curses все это поддерживает, позволяя вам указать атрибут для каждой ячейки экрана.

Атрибут представлен целым числом, где каждый бит соответствует отдельному атрибуту. Вы можете попробовать отобразить текст с несколькими установленными битами атрибутов, но curses не гарантирует, что все комбинации атрибутов допустимы, или что все они визуально различаются. Это зависит от способности используемого терминала, поэтому лучше всего придерживаться использования наиболее часто используемых атрибутов, перечисленных в таблице ниже.

Атрибут Описание
A_BLINK Мигающий текст.
A_BOLD Толстый текст.
A_DIM Текст половинной яркости (затемненный).
A_REVERSE Тест с инверсией чернил и фона.
A_STANDOUT Лучший доступный режим выделения.
A_UNDERLINE Подчеркнутый текст.

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

stdscr.addstr(0, 0, "Текущий режим: ввод текста",
              curses.A_REVERSE)
stdscr.refresh()

Библиотека curses также поддерживает цвет, если терминалы предоставляют такую возможность. Для использования цвета вы должны вызвать функцию start_color() после вызова initscr(), чтобы инициализировать набор цветов по умолчанию (curses.wrapper() делает это автоматически). Как только это сделано, функция has_colors() вернет TRUE, если используемый терминал может отображать цвета.

Примечание: curses использует американское слово 'color', вместо канадского/британского 'colour'.

Библиотека curses поддерживает ограниченное количество пар цветов, содержащих цвет переднего плана, или цвет чернил текста (foreground color), и цвет фона, или бумаги (background color). Вы можете получить значение атрибута, соответствующее цветовой паре с помощью функции color_pair(); его можно совместить операцией побитного ИЛИ (OR) с другими атрибутами, такими как A_REVERSE, однако снова стоит заметить, что такие комбинации могут поддерживаться не всеми терминалами.

Например, следующий код отобразит строку текста с использованием цветовой пары 1:

stdscr.addstr("Привет!", curses.color_pair(1)) stdscr.refresh()

Как уже упоминалось, цветовая пара состоит из цветов foreground и background. Функция init_pair(n, f, b) поменяет цветовую пару n на foreground-цвет f и background-цвет b. Цветовая пара 0 жестко установлена на цвета белый и черный для для чернил и бумаги, и это не может быть изменено.

Цвета пронумерованы, и start_color() инициализирует 8 базовых цветов, когда активирует цветовой режим. Вот они: 0:black, 1:red, 2:green, 3:yellow, 4:blue, 5:magenta, 6:cyan и 7:white. Модуль 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. Это позволит вам изменить color 1, который обычно красный, на пурпурный или синий, или на любой другой выбранный вами цвет. К сожалению, консоль Linux это не поддерживает. Вы можете проверить, может ли ваш терминал использовать цвет RGB, вызовом функции can_change_color(), которая вернет True, если такая возможность есть. Если вам повезло с таким терминалом, то проконсультируйтесь с документацией для получения дополнительной информации.

[Пользовательский ввод]

C-библиотека curses предоставляет очень ограниченные механизмы ввода. Python-модуль curses добавляет базовый виджет ввода текста (другие библиотеки, такие как Urwid, имеют более обширные коллекции виджетов).

Существуют 2 метода получения ввода из окна:

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  # Выход из цикла while, который приведет
               # к завершению программы.
    elif c == curses.KEY_HOME:
        x = y = 0

Модуль curses.ascii предоставляет функции класса ASCII, которые принимают либо целочисленные, либо односимвольные строковые аргументы; они могут быть полезны в написании более удобных проверок в таких циклах. Этот модуль также предоставляет функции преобразования, которые принимают либо целочисленные, либо односимвольные строковые аргументы, и возвращают такой же тип. Например, curses.ascii.ctrl() возвратит символ управления, соответствующий его аргументу.

Существует также метод для возврата всей строки, getstr(). Он не очень часто используется, потому что очень ограничен в функционале; доступны только клавиша редактирования backspace и клавиша Enter, завершающая ввод строки. Опционально ввод может быть ограничен фиксированным количеством символов.

curses.echo()            # разрешает эхо вводимых символов

# Получение 15-символьной строки с курсором, находящимся
# на верхней строке окна: s = stdscr.getstr(0,0, 15)

Модуль curses.textpad предоставляет класс Textbox, который поддерживает привязки клавиш в стиле редактора 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)
# Разрешает пользователю редактирование, пока не будет # нажата комбинация клавиш Ctrl-G: box.edit()
# Возврат содержимого результата: message = box.gather()

Подробности см. в документации curses.textpad.

[Для дополнительной информации]

В этом HOWTO не раскрыты некоторые продвинутые темы, такие как чтение содержимого экрана или захват событий мыши из экземпляра xterm, но а страничке документации Python с описанием модуля curses [4] вы можете найти довольно подробную информацию.

Из-за того, что API библиотеки curses очень обширен, в интерфейсе Python некоторые функции не поддерживаются. Это часто не из-за того, что их трудно реализовать, а просто потому, что пока они никому не нужны. Также Python пока не поддерживает библиотеку меню, связанную с ncurses.

Дополнительные ссылки:

Writing Programs with NCURSES: обширное руководство для программистов на языке C.
Man-документация по ncurses.
ncurses FAQ (What is ncurses?).
• Use curses, don't swear site:youtube.com. Видео PyCon 2013, в котором говорится об управлении терминалом с использованием curses или Urwid.
• Console Applications with Urwid site:youtube.com. Видео PyCon CA 2012, демонстрирующие некоторые приложения, написанные с использованием Urwid.

[Ссылки]

1. Curses Programming with Python site:python.org.
2. windows-curses site:piwheels.org.
3. curses — Terminal handling for character-cell displays.

 

Добавить комментарий


Защитный код
Обновить

Top of Page