Knowledge Base

お知らせや身辺のことを綴っています。
目次
Pythonで対話的UIを実装したいときはreadlineモジュールを使おう

Pythonで対話的UIを実装したいときはreadlineモジュールを使おう

始めに

たまに、Pythonで独自にインタラクティブなインターフェースを実装する必要に駆られる場合があると思います。こういうユーザーの入力を捕らえてしまうデザインパターンはCaptive User Interfaceと言われていて、本当はUnix哲学的によくないとされています。

Avoid captive user interfaces. A program which prevents to user using any other commands for the duration “captures” the user and prevents him from taking advantage of other commands. A program should be usable in many modalities to maximise its usefulness.

Unix Design Philosophy . https://wiki.c2.com/?UnixDesignPhilosophy

しかし、どうしてもという場合は、input()関数を使って以下のようなコードを書くと思います。

while True:
    s = input("what do you want me to echo? > ")
    print(f"me: {s}".format(s))

このコードの問題は、入力時にカーソルを移動しようとしたり、故意にメタ文字を入れようとすると、当たり前のように入ってしまうことです。せめて「カーソル移動ができたらな」と思いませんか?

./test.py
# what do you want me to echo? > ^A^B^E^F^G^[[C^[[D^[[D^[[C^[[D^[[C

GNU Readlineを使おう

こうした場合を想定して、なんとPythonにはポートされたGNU Readlineのライブラリがデフォルトで備わっています。これを使用すると、入力時にBashやEmacsのキーバインディングをそのまま利用できるだけでなく、コマンド履歴も実装することもできてこの上なく便利です。

たとえば、^[b (Alt-B)を押すと一単語だけ元に戻れたり、^A(Ctrl-A)で文頭、^E(Ctrl-E)で文末にジャンプできたりします。そのほかのキーバインディングはBash ReferencesのReadline Interaction節に網羅的に書いてあるので、ぜひ読んでみてください。また、カッコ内のキーは.inputrcによって変えることもできます。

https://tiswww.cwru.edu/php/chet/readline/rluserman.html#Readline-Init-File

プログラムを改良する

さて、先ほどのコマンドを改良してみましょう。以下の公式リファレンスの通りに実装しただけなので、あまり面白くないですが、追加のモジュールを少し読みこんで追記するだけですぐにinput()関数で使えます。

https://docs.python.org/3/library/readline.html

import atexit
import os
import readline

histfile = os.path.join(os.path.expanduser("~"), ".python_history")
try:
    readline.read_history_file(histfile)
    readline.set_history_length(1000)
except FileNotFoundError:
    pass

atexit.register(readline.write_history_file, histfile)

while True:
    s = input("what do you want me to echo? > ")
    print(f"me: {s}".format(s))

おわりに

PythonとGNU周りのソフトウェア資産はすごいと思いました(小並感)

前の記事

6月を振り返ってみる

コメント

0

コメントはありません。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

関連投稿

プログラミング
「subprocessのPopenを使ってプロセス間通信させる」のサムネイル画像

subprocessのPopenを使ってプロセス間通信させる

PythonのsubprocessモジュールのPopenを使って、アプリケーションをパイプでつないでみました。 続きを読む

プログラミング
「ABC329 を Python で解く (AからD問題まで)」のサムネイル画像

ABC329 を Python で解く (AからD問題まで)

AtCoderで解いた問題の振り返りを行うための自分用のメモです。 続きを読む

プログラミング
「PHPでカリー化を使ってみたよ」のサムネイル画像

PHPでカリー化を使ってみたよ

PHPでカリー化を使って、2つの値を記号でつないでくれる関数を作ってみました。 続きを読む

プログラミング
「AtCoder Beginners Contest 303 を PHP で解く」のサムネイル画像

AtCoder Beginners Contest 303 を PHP で解く

AtCoderで解いた問題の振り返りを行うための自分用のメモです。 続きを読む