Pythonを使用したCursesプログラミング—Pythonドキュメント

提供:Dev Guides
< PythonPython/docs/3.8/howto/curses
移動先:案内検索

Pythonを使用したCursesプログラミング

著者
午前 Kuchling、EricS。 レイモンド
リリース
2.04

概要

このドキュメントでは、 curses 拡張モジュールを使用してテキストモードの表示を制御する方法について説明します。


呪いとは何ですか?

cursesライブラリは、テキストベースの端末用に端末に依存しないスクリーンペイントおよびキーボード処理機能を提供します。 このような端末には、VT100、Linuxコンソール、およびさまざまなプログラムによって提供されるシミュレートされた端末が含まれます。 ディスプレイ端末は、カーソルの移動、画面のスクロール、領域の消去などの一般的な操作を実行するためのさまざまな制御コードをサポートしています。 異なる端末は大きく異なるコードを使用し、多くの場合、独自の小さな癖があります。

グラフィックディスプレイの世界では、「なぜわざわざ」と尋ねる人がいるかもしれません。 確かに文字セルディスプレイ端末は時代遅れの技術ですが、それを使って派手なことをできることはまだ価値があるニッチがあります。 1つのニッチは、Xサーバーを実行しない小さなフットプリントまたは組み込みUnixにあります。 もう1つは、グラフィカルサポートが利用可能になる前に実行する必要があるOSインストーラーやカーネルコンフィギュレーターなどのツールです。

cursesライブラリはかなり基本的な機能を提供し、重複しない複数のテキストウィンドウを含む表示の抽象化をプログラマーに提供します。 ウィンドウの内容は、テキストの追加、消去、外観の変更など、さまざまな方法で変更できます。cursesライブラリは、適切な出力を生成するために端末に送信する必要のある制御コードを把握します。 cursesは、ボタン、チェックボックス、ダイアログなどの多くのユーザーインターフェイスの概念を提供しません。 このような機能が必要な場合は、 Urwid などのユーザーインターフェイスライブラリを検討してください。

cursesライブラリは元々BSDUnix用に書かれていました。 AT&TのUnixの新しいSystem Vバージョンでは、多くの機能拡張と新機能が追加されました。 BSD cursesは維持されなくなり、AT&Tインターフェースのオープンソース実装であるncursesに置き換えられました。 LinuxやFreeBSDなどのオープンソースUnixを使用している場合、システムはほぼ確実にncursesを使用します。 現在のほとんどの商用UnixバージョンはSystemVコードに基づいているため、ここで説明するすべての機能がおそらく利用可能になります。 ただし、一部のプロプライエタリUnixに搭載されている古いバージョンのcursesは、すべてをサポートしているわけではありません。

Windows版のPythonには、 curses モジュールは含まれていません。 UniCurses と呼ばれる移植バージョンが利用可能です。 また、Fredrik Lundhによって作成されたコンソールモジュールを試すこともできます。これは、cursesと同じAPIを使用しませんが、カーソルアドレス指定可能なテキスト出力とマウスおよびキーボード入力の完全サポートを提供します。

Pythoncursesモジュール

Pythonモジュールは、cursesによって提供されるC関数のかなり単純なラッパーです。 Cでのcursesプログラミングに既に精通している場合は、その知識をPythonに転送するのは非常に簡単です。 最大の違いは、Pythonインターフェースでは、addstr()mvaddstr()mvwaddstr()などのさまざまなC関数を1つの addstr()にマージすることで作業が簡単になることです。 ] 方法。 これについては、後で詳しく説明します。

このHOWTOは、cursesとPythonを使用したテキストモードプログラムの作成の概要です。 これは、cursesAPIの完全なガイドになることを目的としたものではありません。 そのためには、ncursesに関するPythonライブラリガイドのセクション、およびncursesのCマニュアルページを参照してください。 しかし、それはあなたに基本的な考えを与えるでしょう。


cursesアプリケーションの開始と終了

何かをする前に、cursesを初期化する必要があります。 これは、 initscr()関数を呼び出すことによって実行されます。この関数は、端末タイプを判別し、必要なセットアップコードを端末に送信し、さまざまな内部データ構造を作成します。 成功した場合、initscr()は画面全体を表すウィンドウオブジェクトを返します。 これは通常、対応するC変数の名前にちなんでstdscrと呼ばれます。

import curses
stdscr = curses.initscr()

通常、cursアプリケーションは、キーを読み取って特定の状況でのみ表示できるようにするために、画面へのキーの自動エコーをオフにします。 これには、 noecho()関数を呼び出す必要があります。

curses.noecho()

また、アプリケーションは通常、Enterキーを押すことなく、キーに即座に反応する必要があります。 これは、通常のバッファ入力モードとは対照的に、cbreakモードと呼ばれます。

curses.cbreak()

端末は通常、カーソルキーなどの特殊キーまたはPageUpやHomeなどのナビゲーションキーをマルチバイトエスケープシーケンスとして返します。 このようなシーケンスを予期してそれに応じて処理するようにアプリケーションを作成することもできますが、cursesはそれを実行して、curses.KEY_LEFTなどの特別な値を返します。 呪いをかけて仕事をするためには、キーパッドモードを有効にする必要があります。

stdscr.keypad(True)

cursesアプリケーションの終了は、開始するよりもはるかに簡単です。 電話する必要があります:

curses.nocbreak()
stdscr.keypad(False)
curses.echo()

呪いに適した端末設定を逆にします。 次に、 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 内で呼び出され、例外をキャッチして端末の状態を復元してから、例外を再発生させます。 したがって、端末が例外時におかしな状態のままになることはなく、例外のメッセージとトレースバックを読み取ることができます。


窓とパッド

Windowsはcursesの基本的な抽象化です。 ウィンドウオブジェクトは、画面の長方形の領域を表し、テキストの表示、消去、ユーザーによる文字列の入力などのメソッドをサポートします。

initscr()関数によって返されるstdscrオブジェクトは、画面全体をカバーするウィンドウオブジェクトです。 多くのプログラムでは、この単一のウィンドウのみが必要な場合がありますが、画面を個別に再描画またはクリアするために、画面を小さなウィンドウに分割することをお勧めします。 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.LINESおよびcurses.COLS変数を使用して y および x サイズを取得することにより、画面のサイズを決定できます。 法定座標は(0,0)から(curses.LINES - 1, curses.COLS - 1)に拡張されます。

メソッドを呼び出してテキストを表示または消去しても、効果はすぐにはディスプレイに表示されません。 代わりに、ウィンドウオブジェクトの refresh()メソッドを呼び出して画面を更新する必要があります。

これは、cursesが元々300ボーの低速端末接続を念頭に置いて作成されたためです。 これらの端末では、画面の再描画に必要な時間を最小限に抑えることが非常に重要でした。 代わりに、cursesは画面への変更を蓄積し、refresh()を呼び出すときに最も効率的な方法でそれらを表示します。 たとえば、プログラムがウィンドウにテキストを表示してからウィンドウをクリアした場合、元のテキストは表示されないため、送信する必要はありません。

実際には、ウィンドウを再描画するようにcursesに明示的に指示しても、cursesを使用したプログラミングはそれほど複雑にはなりません。 ほとんどのプログラムは活発な活動を開始し、その後、ユーザー側でのキー押下またはその他のアクションを待つために一時停止します。 他の関連ウィンドウのstdscr.refresh()またはrefresh()メソッドを最初に呼び出すことにより、ユーザー入力を待つために一時停止する前に、画面が再描画されたことを確認するだけです。

パッドはウィンドウの特殊なケースです。 実際の表示画面より大きくすることができ、一度に表示されるパッドの一部のみです。 パッドを作成するにはパッドの高さと幅が必要ですが、パッドを更新するには、パッドのサブセクションが表示される画面上の領域の座標を指定する必要があります。

pad = curses.newpad(100, 100)
# These loops fill the pad with letters; addch() is
# explained in the next section
for y in range(0, 99):
    for x in range(0, 99):
        pad.addch(y,x, ord('a') + (x*x+y*y) % 26)

# Displays a section of the pad in the middle of the screen.
# (0,0) : coordinate of upper-left corner of pad area to display.
# (5,5) : coordinate of upper-left corner of window area to be filled
#         with pad content.
# (20, 75) : coordinate of lower-right corner of window area to be
#          : filled with pad content.
pad.refresh( 0,0, 5,5, 20,75)

refresh()呼び出しは、画面上で座標(5,5)から座標(20,75)まで伸びる長方形のパッドのセクションを表示します。 表示されたセクションの左上隅は、パッド上の座標(0,0)です。 その違いを除けば、パッドは通常のウィンドウとまったく同じであり、同じ方法をサポートします。

画面に複数のウィンドウとパッドがある場合は、画面を更新して、画面の各部分が更新されるときに画面がちらつくのを防ぐためのより効率的な方法があります。 refresh()は実際には2つのことを行います。

  1. 各ウィンドウの noutrefresh()メソッドを呼び出して、画面の目的の状態を表す基になるデータ構造を更新します。
  2. 関数 doupdate()関数を呼び出して、データ構造に記録されている目的の状態に一致するように物理画面を変更します。

代わりに、いくつかのウィンドウでnoutrefresh()を呼び出してデータ構造を更新してから、doupdate()を呼び出して画面を更新することができます。


テキストの表示

Cプログラマーの観点からは、呪いは関数のねじれた迷路のように見えることがあり、すべて微妙に異なります。 たとえば、addstr()は、stdscrウィンドウの現在のカーソル位置に文字列を表示しますが、mvaddstr()は、文字列を表示する前に、最初に指定されたy、x座標に移動します。 waddstr()addstr()と同じですが、デフォルトでstdscrを使用する代わりに使用するウィンドウを指定できます。 mvwaddstr()では、ウィンドウと座標の両方を指定できます。

幸い、Pythonインターフェースはこれらすべての詳細を隠します。 stdscrは他のオブジェクトと同様にウィンドウオブジェクトであり、 addstr()などのメソッドは複数の引数形式を受け入れます。 通常、4つの異なる形式があります。

説明
str または ch 文字列 str または文字 ch を現在の位置に表示します
str または chattr 現在の位置で属性 attr を使用して、文字列 str または文字 ch を表示します
yxstr または ch ウィンドウ内の位置 y、x に移動し、 str または ch を表示します。
yxstr または chattr ウィンドウ内の位置 y、x に移動し、属性 attr を使用して str または ch を表示します。

属性を使用すると、太字、下線、逆コード、またはカラーなどの強調表示された形式でテキストを表示できます。 これらについては、次のサブセクションで詳しく説明します。

addstr()メソッドは、表示される値としてPython文字列またはバイト文字列を取ります。 バイトストリングの内容はそのまま端末に送信されます。 文字列は、ウィンドウのencoding属性の値を使用してバイトにエンコードされます。 これは、 locale.getpreferredencoding()によって返されるデフォルトのシステムエンコーディングにデフォルト設定されます。

addch()メソッドは、長さ1の文字列、長さ1のバイト文字列、または整数のいずれかである文字を取ります。

拡張文字には定数が用意されています。 これらの定数は255より大きい整数です。 たとえば、ACS_PLMINUSは+/-記号であり、ACS_ULCORNERはボックスの左上隅です(境界線の描画に便利です)。 適切なUnicode文字を使用することもできます。

Windowsは、最後の操作の後にカーソルが置かれた場所を記憶しているため、 y、x 座標を省略すると、最後の操作が中断された場所に文字列または文字が表示されます。 move(y,x)方式でカーソルを移動することもできます。 一部の端末では常に点滅するカーソルが表示されるため、カーソルが邪魔にならない場所に配置されていることを確認することをお勧めします。 明らかにランダムな場所でカーソルが点滅するのは混乱を招く可能性があります。

アプリケーションでカーソルの点滅がまったく必要ない場合は、curs_set(False)を呼び出して非表示にすることができます。 古いcursesバージョンとの互換性のために、 curs_set()の同義語であるleaveok(bool)関数があります。 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コンソールであり、次にcolorxtermsが続きます。

色を使用するには、 initscr()を呼び出した直後に start_color()関数を呼び出して、デフォルトの色セット( curses.wrapper())を初期化する必要があります。関数はこれを自動的に行います)。 それが完了すると、使用中の端末が実際に色を表示できる場合、 has_colors()関数はTRUEを返します。 (注:cursesは、カナダ式/英国式のスペル「color」ではなく、アメリカ式のスペル「color」を使用します。 英国式のつづりに慣れている場合は、これらの機能のためにつづりを間違えることを辞任する必要があります。)

cursesライブラリは、前景色(またはテキスト)の色と背景色を含む、有限数の色のペアを維持します。 color_pair()関数を使用して、色のペアに対応する属性値を取得できます。 これは、A_REVERSEなどの他の属性とビットごとにORをとることができますが、このような組み合わせがすべての端末で機能することは保証されていません。

色のペア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_BLACKcurses.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が返されます。 幸運にもそのような才能のある端末を持っている場合は、システムのマニュアルページで詳細を確認してください。


ユーザー入力

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()にタイマーを設定できます。 指定された遅延(10分の1秒単位で測定)内に入力が利用可能にならない場合、cursesは例外を発生させます。

getch()メソッドは整数を返します。 0〜255の場合は、押されたキーのASCIIコードを表します。 255より大きい値は、Page Up、Home、またはカーソルキーなどの特殊キーです。 curses.KEY_PPAGEcurses.KEY_HOMEcurses.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 モジュールは、整数または1文字の文字列引数をとるASCIIクラスメンバーシップ関数を提供します。 これらは、そのようなループに対してより読みやすいテストを作成するのに役立つ場合があります。 また、整数または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 のライブラリドキュメントを参照してください。


詳細については

このHOWTOは、画面のコンテンツの読み取りやxtermインスタンスからのマウスイベントのキャプチャなど、一部の高度なトピックについては説明していませんが、 curses モジュールのPythonライブラリページはかなり完成しています。 次にそれを閲覧する必要があります。

curses関数の詳細な動作について疑問がある場合は、ncursesであるか独自のUnixベンダーであるかにかかわらず、cursesの実装のマニュアルページを参照してください。 マニュアルページには、癖があれば文書化され、使用可能なすべての機能、属性、およびACS_*文字の完全なリストが提供されます。

curses APIは非常に大きいため、一部の関数はPythonインターフェースでサポートされていません。 多くの場合、これは実装が難しいためではなく、まだ誰も必要としていないためです。 また、Pythonはncursesに関連付けられたメニューライブラリをまだサポートしていません。 これらのサポートを追加するパッチは大歓迎です。 Pythonへのパッチの送信の詳細については、 Python開発者ガイドを参照してください。