目次


魅力的なPython: cursesプログラミング

初心者向けのヒント

Comments

curses ライブラリー (ncurses) は、文字画面を制御する、端末に依存しない方法を提供します。curses は、Linux を含む、ほとんどの UNIXベース・システムの標準パーツですが、Windows やその他のシステムにも移植されてます。curses プログラムは、テキストのみのシステム上でも、xterm やその他のウィンドウ化コンソール・セッション内でも実行できるので、これらのプログラムの移植性は非常に高くなります。

curses の紹介

CRT が広まった 1970 年代に、最初の curses ライブラリーが作成されたましたが、以来 Python の標準 curses は、「ガラスのテレタイプ」の共通機能に対し基本インターフェースを提供してきました。Python で作成した対話式のテキスト・モード・プログラムに高度な機能を加える方法はたくさんあります。これらは、2 つのカテゴリーに分類されます。

1 つは、ncurses (curses のスーパーセット) またはslang (似ているが独立したコンソール・ライブラリー) の完全な機能セットをサポートする Python モジュールです。中でも特記すべきは、(適切な Python モジュールでラップされた) これらの拡張ライブラリーの 1 つを使うことで、インターフェースに色を加えることができます。

もう 1 つは、curses (またはncurses/slang) の上層に作成された、数多くのハイレベルの widget ライブラリーを用いて、ボタン、メニュー、スクロール・バーなど、各種の共通インターフェース部品の機能を追加します。Borland の TurboWindows (DOS 版) などのライブラリーを使って開発したアプリケーションを見ると、これらの機能がテキスト・モードのコンソールでいかに魅力的であるかがわかります。widget ライブラリー内にあるものはすべて、curses だけで実行できるだけでなく、他のプログラマーがハイレベルのインターフェースで実行した作業も利用できます。紹介したモジュールへのリンクについては、参考文献のセクションを参照してください。

ここでは、curses そのものの機能しか扱いません。curses モジュールは標準配布の一部であるので、サポート・ライブラリーやその他の Python モジュールを (少なくとも Linux システムや UNIX システム上で) ダウンロードしなくても、利用し、機能させることができます。よりハイレベルのモジュールを理解する場合には、ベースにしかならないかもしれませんが、curses によって提供される基本サポートを理解しておくことは役に立ちます。こうした他のモジュールを使用しない場合は、Python で curses だけを使用して、魅力的で役に立つテキスト・モード・アプリケーションを非常に簡単に作成することができます。プレリリース・ノートでは、Python 2.0 には curses の拡張バージョンが組み込まれると示されていますが、このバージョンは、いずれにせよ、ここで説明したバージョンとの下位互換性を確保しているはずです。

アプリケーション

テスト・アプリケーションとして、Txt2Html (テキストから HTML への変換プログラム、「魅力的な Python: 私の最初の Web ベース・フィルター・プロキシー」を参照) 用に作成したラッパーを紹介しましょう。Txt2Html を動作させる方法はいくつかあります。しかし、ここでは、Txt2Html はコマンド行から実行するものとします。Txt2Html を動作させる 1 つの方法として、実行する変換のさまざまな詳細を指定するコマンド行引数を提供し、アプリケーションをバッチ・プロセスとして実行する方法があります。また、もっと使いやすいインターフェースがあれば、実際の変換を実行する前に、変換オプションを示す対話形式の選択画面 (選択されたオプションのフィードバックをビジュアルで提供する画面) をユーザーに表示することもできます。

curses_txt2html のインターフェースは、ドロップダウンとネスト・サブメニューからなる、おなじみのトップバー・メニューをベースとしています。メニュー関連の機能はすべて、curses の上層で「最初から」実行されました。これらのメニューにはもっと高度な curses ラッパーの機能がないので、curses だけを使用して、適度な数の行で基本機能が実装されます。また、このインターフェースには、簡単なスクロール・ヘルプ・ボックスと複数のユーザー入力フィールドがあるという特徴もあります。次に、レイアウトとスタイルの概要を示すアプリケーションのスクリーン・ショットを示します。

X 端末上のアプリケーション
curses_txt2html.py のスクリーン・ショット
curses_txt2html.py のスクリーン・ショット
Linux 端末上のアプリケーション
大きいコンソールのスクリーン・ショット
大きいコンソールのスクリーン・ショット

curses アプリケーションのラッピング

curses プログラミングの基本要素はウィンドウ・オブジェクトです。ウィンドウとは、座標がそのウィンドウを基準としているアドレッシング可能カーソルを持つ、実際の物理的な画面の領域です。ウィンドウは移動することができ、他のウィンドウに依存することなく作成したり削除したりすることができます。ウィンドウ・オブジェクト内では、カーソルの場所で入出力アクションが発生します。入出力アクションは、通常は入出力メソッドにより明示的に設定されますが、個別に変更することもできます。

curses を初期設定すると、ストリーム方式のカーソル入出力がさまざまな方法で変更されたり、または完全に使用不能となります。これは、基本的には、使用している curses に完全に依存します。しかし、ストリーム方式のコンソール対話がいったん変更されると、Python トレースバック・イベントは、プログラム・エラーの場合に通常の方法では表示されません。Andrew Kuchling は、この問題を curses プログラムの優れた最上位のフレームワークを用いて解決しています (チュートリアルについては、参考文献を参照してください)。

次に、標準コマンド行 Python のエラー報告機能を保持するテンプレートを示します (基本的には Kuchling のものと同じです)。

Python [curses] プログラムの最上位のセットアップ・コード
import  curses, traceback
 if  __name__== '__main__' :
 try :
 # Initialize curses 
      stdscr=curses.initscr()
 # Turn off echoing of keys, and enter cbreak mode, 
 # where no buffering is performed on keyboard input 
      curses.noecho()
      curses.cbreak()
 # In keypad mode, escape sequences for special keys 
 # (like the cursor keys) will be interpreted and 
 # a special value like curses.KEY_LEFT will be returned 
      stdscr.keypad(1)
      main(stdscr) # Enter the main loop 
 # Set everything back to normal 
      stdscr.keypad(0)
      curses.echo()
      curses.nocbreak()
      curses.endwin() # Terminate curses 
 except :
 # In event of error, restore terminal to sane state. 
      stdscr.keypad(0)
      curses.echo()
      curses.nocbreak()
      curses.endwin()
      traceback.print_exc() # Print the exception

try ブロックは、初期設定を実行し、main() 関数を呼び出して実際の作業を実行してから、最終的なクリーンアップを実行します。何か問題がある場合は、except ブロックがコンソールをデフォルト状態に戻し、発生した例外を報告します。

main() イベント・ループ

次に、main() 関数を見て、curses_txt2html が何を実行するかを確認しましょう。

curses_txt2html.py main() 関数とイベント・ループ
def main(stdscr):
    # Frame the interface area at fixed VT100 size
    global screen
    screen = stdscr.subwin(23, 79, 0, 0)
    screen.box()
    screen.hline(2, 1, curses.ACS_HLINE, 77)
    screen.refresh()
     
# Define the topbar menus
    file_menu = ("File","file_func()")
    proxy_menu = ("Proxy Mode", "proxy_func()")
    doit_menu = ("Do It!", "doit_func()")
    help_menu = ("Help", "help_func()")
    exit_menu = ("Exit", "EXIT")
    # Add the topbar menus to screen object
    topbar_menu((file_menu, proxy_menu, doit_menu,
                 help_menu, exit_menu))
    
# Enter the topbar menu loop
    while topbar_key_handler():
        draw_dict()

main() 関数は、3 つのセクションに分ける (ブランク行で区切る) ことで、簡単に理解することができます。

1 つ目のセクションは、アプリケーションの外観の一般的なセットアップを実行します。アプリケーション要素間の予測可能なスペーシングを設定するため、対話式エリアは (実際の端末ウィンドウがもっと大きい場合でも) 80 x 25 の VT100/PC 画面サイズに制限されます。このサブウィンドウの回りに枠が作成され、トップバー・メニューの表示オフセットには水平線が使用されます。

2 つ目のセクションは、アプリケーションが使用するメニューを設定します。関数topbar_menu() は、ホット・キーをアプリケーション・アクションに結合する際、および指定された表示属性でメニューを表示する際にちょっとしたテクニックを使います。ソース・アーカイブをチェックし (参考文献を参照)、コード全体を確認してください。topbar_menu() はかなり汎用的であるはずです (自分のアプリケーションに自由に取り入れて構いません)。大切なのは、メニューに関連付けられているタプルの 2 つ目の要素にどのようなストリングが入っている場合でも、ホット・キーを結合するとこれらのストリングに対してeval() が実行される、ということです。たとえば、上記のセットアップで "File" メニューをアクティブにすると、"eval("file_func()")" が呼び出されます。そのため、アプリケーションは、file_func() という関数を定義し、アプリケーションの終了状態に達したかどうかを示すブール値を戻す必要があります。

3 つ目のセクションには、2 行しかありませんが、アプリケーション全体が実際に実行されるのはここです。関数topbar_key_handler() の動作は、その名前が示すとおりです。キーストロークを待機してから、それらを処理します。キー・ハンドラーは、ブール値 false を戻すことがあります (その場合には、アプリケーションは終了します)。このアプリケーションでは、キー・ハンドラーは、2 つ目のセクションで結合されたキーのチェックから成り立ちます。しかし、curses アプリケーションはこのようにキーを結合しなかった場合でも、同じイベント・ループを使用することができます。大事なポイントは、ハンドラーはおそらく次のような行を使用するということです。

c = screen.getch()   # read a keypress

draw_dict() 呼び出しは、イベント・ループ内に直接ある唯一のコードです。この関数は、screen ウィンドウのいくつかの場所に値を描画します。しかし、ユーザーのアプリケーションでは、おそらく次のような行を描画/更新関数の内部 (あるいは、単にイベント・ループそのものの内部) に挿入する必要があります。

screen.refresh()   # redraw the screen w/ any new output

ユーザー入力の取得

curses アプリケーションは、そのユーザー入力すべてをキープレス・イベントの形式で取得します。.getch() メソッドについてはすでに説明したので、.getch() とそのほかの入力メソッド.getstr() を組み合わせた例を紹介しましょう。前に紹介したfile_func() 関数の省略バージョンを示します (これは、"File" メニューによってアクティブにされます)。

curses_txt2html.py file_func() 関数
def file_func():
  s = curses.newwin(5,10,2,1)
  s.box()
  s.addstr(1,2, "I", hotkey_attr)
  s.addstr(1,3, "nput", menu_attr)
  s.addstr(2,2, "O", hotkey_attr)
  s.addstr(2,3, "utput", menu_attr)
  s.addstr(3,2, "T", hotkey_attr)
  s.addstr(3,3, "ype", menu_attr)
  s.addstr(1,2, "", hotkey_attr)
  s.refresh()  c = s.getch()  
if c in (ord('I'), ord('i'), curses.KEY_ENTER, 10):
      curses.echo()
      s.erase()
      screen.addstr(5,33, " "*43, curses.A_UNDERLINE)
      cfg_dict['source'] = screen.getstr(5,33)
      curses.noecho()
  else:
      curses.beep()
      s.erase()
  return CONTINUE

この関数は、複数の curses 機能を組み合わせたものです。最初に行うのは、別のウィンドウ・オブジェクトの作成です。この新しいウィンドウ・オブジェクトは "File" 選択の実際のドロップダウン・メニューなので、.box() メソッドを使ってそのオブジェクトの回りに枠が作成されます。ウィンドウs 内では、ドロップダウン・メニューのオプションが作成されます。各オプションのホット・キーが、残りのオプション説明と対比してハイライトされるように、少し面倒なメソッドを使用します (ハイライトの自動化処理については、完全なソース (参考文献 を参照) 内のtopbar_menu() を参照してください)。最後の.addstr() 呼び出しにより、カーソルはデフォルトのメニュー・オプションに移動します。メイン画面と同様に、s.refresh() は実際には、ウィンドウ・オブジェクトに描画された要素を表示します。

ドロップダウン・メニューの作成後、プログラムは、簡単なs.getch() 呼び出しを用いてユーザーの選択を取得します。このアプリケーション例では、メニューはホット・キーにのみ応答し、矢印キーの選択や移動可能なハイライト・バーには応答しません。これらの高度なメニュー操作機能は、追加キー・アクションを捕そくし、ドロップダウン・メニュー内でイベント・ループをセットアップすることで作成できます。しかし、概念を説明するには、この例だけで十分です。

次に、プログラムは、読み取ったキーストロークを各種のホット・キー値と比較します。この場合、ドロップダウン・メニュー・オプションはホット・キーの大文字/小文字バージョンによってアクティブにでき、デフォルト・オプションは ENTER (実行) キーでアクティブにできます (curses の特殊なキー定数は、完全には信頼できないようなので、ENTER (実行) キーをトラップするためには、実際の ASCII 値 "10" を追加しなければならないことがわかりました)。文字値との比較を実行する場合は、文字のストリングをord() 組み込み Python 関数でラップする必要があることに注意しましょう。

"Input" オプションが選択されると、不完全な編集機能と共にフィールド入力を提供する、.getstr() メソッドが使用されます (バックスペース・キーを使用できます)。入力の最後に ENTER (実行) キーを押します。メソッドは、入力されたどのような値でも戻します。この値は、上記の例のように、一般的には変数に代入されます。

入力フィールドを見分けることができるように、少しトリックを使用して、データ入力が発生する場所にあらかじめ下線を引いておきました。これは絶対に必要なわけではありませんが、こうしておけば少し見やすくなります。下線は、次の行により実行されます。

screen.addstr(5,33, " "*43, curses.A_UNDERLINE)

もちろん、次の行を追加して、draw_dict() 最新表示関数内で実行される、フィールド入力の強調も削除しなければなりません。

screen.addstr(5,33, " "*43, curses.A_NORMAL)

まとめ

ここで紹介したテクニックと、完全なアプリケーション・ソース・コードで使用したテクニック (参考文献を参照) を習得すれば、curses プログラミングを始めることができるでしょう。すこし、練習してみてください。難しいことではありません。1 つの特徴として、curses ライブラリーは Python 以外の多くの言語でも使用できるので、Python の curses モジュールの使って覚えたことは、他の言語でも役立つでしょう。

基本 curses モジュールだけでは物足りない場合は、参考文献のセクションを参照してください。curses の機能を充実させ、拡張をより簡単にする、数多くのモジュールへのリンクが収められています。


ダウンロード可能なリソース


関連トピック

  • Andrew Kuchling は、Curses Programming With Python という本で、curses プログラミングの入門チュートリアルを執筆しました。ここでは、curses プログラミングのさまざなな要素 (大半は高度なレベル) を扱っていますが、その一部は、Kuchling の例の影響を受けています。
  • Python のテキスト・ベースのユーザー・インターフェース・ツールに関する最適な入門ページを参照してください。
  • Python のncurses は、Python 1.5.2 curses よりも広範囲のncurses 機能をサポートする拡張モジュールです。Python 2.0 のncurses には、curses に代わってncurses を使用するための予備計画があります。
  • pcrt は、直接の ANSI エスケープ・コード画面アクセスのモジュールです。これは、特定の色と属性を用いて、画面上の特定の場所に書き込みます。下位のインターフェースで (curses よりもさらに下位)、ANSI エスケープ・コードをサポートするコンソール (大半がそうです) でしか動作しませんが、テキスト・モード・アプリケーションの外観を美しくする優れた方法です。
  • dialog は、Linuxdialog ユーティリティーを囲む Python ラッパーです。このユーティリティーを (Python ラッパーと共に) 使用することで、はい/いいえ、メニュー、入力、メッセージ、テキスト、情報チェックリスト、ラジオリストの各ダイアログを作成できます。プラットフォーム制限が問題でなければ、このユーティリティーとモジュールを使用して、多くのことを非常に短時間で実行することができます (もちろん、ターゲットの Linux 配布にdialog が入っていなければなりません)。
  • ここで使用し、紹介したファイルをダウンロードしてください。

コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=230457
ArticleTitle=魅力的なPython: cursesプログラミング
publish-date=09012000