Проклинает программирование на Python

Автор:

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

Выпускать:

2.04

Что такое проклятия?

Библиотека curses предоставляет независимые от терминала средства рисования экрана и работы с клавиатурой для текстовых терминалов; к таким терминалам относятся VT100s, консоль 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-версиями, могут поддерживать не все.

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

Модуль проклятий Python

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

Это руководство представляет собой введение в написание программ в текстовом режиме с использованием curses и Python. Оно не претендует на то, чтобы быть полным руководством по curses API; для этого смотрите раздел руководства по библиотеке Python, посвященный ncurses, и страницы руководства по C для ncurses. Однако это даст вам основные идеи.

Запуск и завершение работы приложения curses

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

import curses
stdscr = curses.initscr()

Обычно приложения curses отключают автоматическое отображение клавиш на экране, чтобы иметь возможность считывать клавиши и отображать их только при определенных обстоятельствах. Для этого требуется вызвать функцию noecho().

curses.noecho()

Приложениям также обычно требуется мгновенно реагировать на нажатие клавиш, не требуя нажатия клавиши Enter; это называется режимом cbreak, в отличие от обычного режима буферизованного ввода.

curses.cbreak()

Терминалы обычно возвращают специальные клавиши, такие как клавиши управления курсором или навигационные клавиши, такие как Page Up и Home, в виде многобайтовой управляющей последовательности. Хотя вы могли бы написать свое приложение так, чтобы оно ожидало таких последовательностей и обрабатывало их соответствующим образом, curses может сделать это за вас, возвращая специальное значение, например curses.KEY_LEFT. Чтобы заставить curses выполнять эту работу, вам нужно включить режим клавиатуры.

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

Окна и накладки

Окна являются основной абстракцией в curses. Объект window представляет собой прямоугольную область экрана и поддерживает методы отображения текста, его стирания, позволяет пользователю вводить строки и так далее.

Объект stdscr, возвращаемый функцией initscr(), является объектом window, который занимает весь экран. Многим программам может потребоваться только это одно окно, но вы можете захотеть разделить экран на окна меньшего размера, чтобы перерисовывать или очищать их по отдельности. Функция newwin() создает новое окно заданного размера, возвращая объект new window.

begin_x = 20; begin_y = 7
height = 5; width = 40
win = curses.newwin(height, width, begin_y, begin_x)

Обратите внимание, что система координат, используемая в курсах, необычна. Координаты всегда передаются в порядке y,x, а верхний левый угол окна - это координата (0,0). Это нарушает обычное соглашение о работе с координатами, где координата x стоит на первом месте. Это досадное отличие от большинства других компьютерных приложений, но оно было частью curses с момента ее первого написания, и сейчас уже слишком поздно что-либо менять.

Ваше приложение может определить размер экрана, используя переменные curses.LINES и curses.COLS для получения размеров y и x. Юридические координаты будут изменены с (0,0) на (curses.LINES - 1, curses.COLS - 1).

Когда вы вызываете метод для отображения или удаления текста, эффект не сразу проявляется на экране. Вместо этого вы должны вызвать метод refresh() объектов window для обновления экрана.

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

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

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

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

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

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

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

К счастью, интерфейс Python скрывает все эти детали. stdscr - это такой же объект window, как и любой другой, и такие методы, как 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 или bytestring в качестве отображаемого значения. Содержимое байтовых строк отправляется в терминал как есть. Строки кодируются в байтах с использованием значения атрибута window encoding; по умолчанию используется системная кодировка по умолчанию, возвращаемая locale.getencoding().

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

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

Windows запоминает, где был оставлен курсор после последней операции, поэтому, если вы не укажете координаты 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, за которой следуют термины цвета.

Чтобы использовать color, вы должны вызвать функцию 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 предлагает только очень простые механизмы ввода. Модуль curses в Python добавляет базовый виджет для ввода текста. (Другие библиотеки, такие как 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(). Он используется не очень часто, поскольку его функциональность довольно ограничена; единственными доступными клавишами редактирования являются клавиша возврата и клавиша 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_*.

Из-за большого размера curses API некоторые функции не поддерживаются в интерфейсе Python. Часто это происходит не потому, что их сложно реализовать, а потому, что они еще никому не были нужны. Кроме того, Python пока не поддерживает библиотеку меню, связанную с ncurses. Исправления, добавляющие поддержку для них, приветствовались бы; смотрите the Python Developer’s Guide, чтобы узнать больше о том, как отправлять исправления в Python.

Вернуться на верх