naritoブログ

このブログはPython/DjangoとBootstrap4で作成されました
Githubのソースコード

Pythonで、cursesモジュールを使う

約211日前 2018年1月16日23:45
プログラミング関連
Python
今回は標準ライブラリにあるcursesモジュールを使い、CUIアプリケーションを作ります。cursesモジュールを使うと何ができるかと言うと、組み込み関数のinputとprintで作るCUIアプリケーションよりも高度なモノが作成できます。vimのようなエディタも作成ができます。C言語のcurses関連を扱った人ならば、すぐに扱えるようになるでしょう。

GUIなアプリケーションが当たり前の時代ですが、CUIベースのアプリケーションも何だかんだで需要があります。例えば、そもそもGUI環境がないケース...GUI環境が導入される前に動かす必要があったり、マシンの用途としてGUI環境を整えたくない場合もあります(Webサーバーとして使いたいLinuxマシン等)

CUIアプリケーション全般に言えることですが、GUIを使うよりも少ないリソースで動作可能であり、インストールに失敗することもそうありません。HTML・CSS・JavaScriptを覚える必要もありませんし、GUIライブラリのルールを覚える必要もなく、個人的にはプログラミング初心者にオススメしたいです。

cursesはルールをちょっと覚える必要はありますが、そこまで複雑ではありません。さっそくやっていきましょう。

このコードは実行しなくてよいです。理由は後述
import curses

stdscr = curses.initscr()
curses.noecho()
curses.cbreak()
stdscr.keypad(True)



cursesプログラミングをするにあたって、まずは初期化処理をします。それがcurses.initscr()です。画面全体を表すウィンドウオブジェクトを返ってくるのですが、このオブジェクトに対して文字を追加してみたり、色々と処理を行っていくのが基本となります。
stdscr = curses.initscr()


input関数を使った文字入力では、入力文字が自動で画面に表示されていました。これはエコーモード等と呼ぶ状態で、モノによっては入力内容をエコーしてくれて問題ないですが、エコーしないで欲しい場合も多くあります。入力内容を勝手に画面に表示してほしくないならば、noechoを呼び出します。
curses.noecho()


今まではエンターキーを押すことで入力内容を伝えていましたが、これが邪魔な場合もあります。いちいちエンターを押さなくても良いようにしたいならばcbreak()を呼び出します。
curses.cbreak()



Page Up、Home といった特別なキーを「 curses.KEY_LEFT 」といった解りやすい形で判断できたら楽ですね。stdscr.keypad(True)とすることで、特殊なキーが押されても処理しやすくなると思ってください。
stdscr.keypad(True)


実際の処理を紹介していきたいのですが...その前に一つ問題があります。それは、cursesアプリケーションを終了する前に以下の処理で元の状態に戻す必要があるのです。
curses.nocbreak()
stdscr.keypad(False)
curses.echo()


これらを忘れてしまうと....実際にやってみると解りやすいのですが、コマンドプロンプト等の入力内容は自動表示されなくなっちゃうし、何かもう色々とおかしくなります。そのウィンドウを閉じて、また新しくコマンドプロンプト等を開かなければなりません。上の処理を書いたにしても、cursesアプリケーション内で、何らかのエラーで強制終了してしまうことも考えられます。


そこで、以下のようなコードにします。curses.wrapper関数は、上に書いた処理を自動でやってくれます。エラーで強制終了しそうになっても、終了前に設定を戻してくれます。
import curses


def main(stdscr):
    # ここに処理を書いていく...
    pass


if __name__ == '__main__':
    curses.wrapper(main)




初期設定や後片付けはcurses.wrapeerに任せ、実際にいくつか処理を書いていきましょう。処理内容はコメントのとおりです。
import curses


def main(stdscr):
    stdscr.clear()  # 画面のクリア
    stdscr.addstr(0, 0, 'やっほー')  # 0行目, 0番目 に「やっほー」と表示する
    stdscr.refresh()  # 画面の更新
    stdscr.getkey()  # 入力内容の待ち受け。プログラムの終了を防ぐための記述


if __name__ == '__main__':
    curses.wrapper(main)


表示はこのようになります。



getkeyだけは少し説明しておきます。コメントにもありますが、これはプログラムの終了を防ぐための記述です。この行がないと、他に処理がないのでプログラムがそのまま終わってしまい、閉じられます。getkeyはキー入力があるまで待機し、入力されたキーを返します。これによって、キー入力されるまでプログラムを継続させているというわけです。
stdscr.getkey()



addstrの引数には、(y, x, string)という形で引数を渡せます。cursesでは原則(y座標, x座標)とy座標の指定が先になります。y,x引数を省略するとカーソル位置にそのまま文字列が入ります。
def main(stdscr):
    stdscr.clear()  # 画面のクリア
    stdscr.addstr('はろー')
    stdscr.addstr('わーるど')
    stdscr.refresh()  # 画面の更新
    stdscr.getkey()  # 入力内容の待ち受け。プログラムの終了を防ぐための記述





addchによる、一文字の入力もできます。引数はaddstrと同じです。
def main(stdscr):
    stdscr.clear()  # 画面のクリア
    stdscr.addch(0, 0, 'あ')  # 0行0番目
    stdscr.addch(0, 1, 'い')  # 0行1番目
    stdscr.addch(0, 2, 'う')  # 0行2番目
    stdscr.addch(1, 0, 'え')  # 1行0番目
    stdscr.addch('お')  # 結果的に、1行1番目。↑の次の座標に追加される
    stdscr.refresh()  # 画面の更新
    stdscr.getkey()  # 入力内容の待ち受け。プログラムの終了を防ぐための記述




他にもいくつか簡単に紹介します。
# 入力された文字の取得を行う。整数を返す(Unicode コードポイント)
key = stdscr.getch()

# getchと似ているが、整数ではなく文字を返す。特殊文字は「^C」や「KEY_UP」となる
# 日本語のようなマルチバイト文字は、getchのほうが安定すると思う
key = stdscr.getkey()

# 現在のカーソル位置の取得
now_y, now_x = stdscr.getyx()

# ウィンドウの高さおよび幅を取得
max_y, max_x = stdscr.getmaxyx()

# 文字の削除。y, xでの指定がなければ、カーソル位置の文字を削除する
stdscr.delch()

# カーソルの移動
stdscr.move(y, x)



他にも沢山あります。興味があればドキュメントを見てみましょう。
公式ドキュメント:https://docs.python.org/ja/3.6/library/curses.html
公式チュートリアル: https://docs.python.org/ja/3.6/howto/curses.html#curses-howto