Stimulator

機械学習とか好きな技術話とかエンジニア的な話とかを書く

python prompt toolkitの紹介と動作を理解するメモ

- はじめに -

本記事はpython prompt toolkit(以下 ptk)の動作を理解するメモです。

ptkで出来ること、その概要を書いています。

- ptkとは -

ptkは、Pythonで実装されているCLI用のツールキットである。

  • コンソール上でのフルスクリーンアプリケーションの作成
  • dialogs、progress-barの生成
  • シェルのようなインタラクティブな入出力
  • 補完
  • clipboardの管理
  • 出力テキスト色の変更
  • シンタックスハイライト
  • EmacsとViのキーバインディング
  • マウスカーソルによる操作
  • 各種非同期処理

上記のような機能がサポートされており、コンソール上で様々なツールをPythonを利用して作成する事ができる。

github.com

Pure Pythonで書かれている事から、UnixWindowsなどクロスプラットフォームで動作する事、数の多いPyhtonライブラリを資産として利用できる事が強いメリットとして挙げられる。

    
examplesに様々なCLIアプリケーションの例とtutorialが示されており、小さなCLIツールから作成を始める事ができる。

examples: python-prompt-toolkit/examples at master · prompt-toolkit/python-prompt-toolkit · GitHub

 
例えば、シンプルなプログレスバーは、以下程のスクリプトで実装する事ができるだけでなく、プログレスバーの色付けや非同期化、ネスト化も簡易に実装する事ができる。

import time
from prompt_toolkit.shortcuts import ProgressBar

with ProgressBar() as pb:
    for i in pb(range(800)):
        time.sleep(.01)

f:id:vaaaaaanquish:20190706152405p:plainf:id:vaaaaaanquish:20190706152410p:plain


 
入出力のサポートでは、公式の補完の図が分かりやすいため引用しておく。

f:id:vaaaaaanquish:20190706152545p:plain
https://github.com/prompt-toolkit/python-prompt-toolkit

これらの機能は、Jupyter Notebookのに代表されるjupyter_console等でも利用されており、utilityとして多くのツールに導入されている。「xonsh」と呼ばれるシェルもまたこのptkをコアなライブラリとして作成されている。キーバインドや補完、表示の多くをこのptkに頼っている。

github.com

こういったシェル上でのI/O、Validate、表示形式を様々な形でサポートする他、ウィンドウサイズの計算も考慮されており、CLI上でのフルスクリーンアプリケーションを作成することも可能である。



フルスクリーンアプリケーションでは、CLIを縦横に分割したり、WebサイトのようにAlignさせたりといった画面上の計算をサポートしてくれる。

f:id:vaaaaaanquish:20190706140310p:plainf:id:vaaaaaanquish:20190706140317p:plain
vertical-split, horizontal-align

他にも例えばコンソール上で以下のようなものが「ウィジェット」としてサポートされており、Pythonで簡単に実装する事ができる。

f:id:vaaaaaanquish:20190706135444p:plainf:id:vaaaaaanquish:20190706135528p:plain
左:入力やチェックボックスを含むフルスクリーンダイアログアプリケーション
右:プログレスバーを含むフルスクリーンアプリケーション


  

ptkで作成されたツール

フルスクリーンアプリケーションを作成する機能を利用して、Python REPLの立ち位置のptpython、pure pythonなtmuxであるpymux、pure pythonvimであるpyvimをptkと同じ人物が作成している。

github.com

f:id:vaaaaaanquish:20190706154214p:plainf:id:vaaaaaanquish:20190706154204p:plain
ptkを利用した画面分割やフロートウィンドウ

github.com

github.com

f:id:vaaaaaanquish:20190706154001p:plainf:id:vaaaaaanquish:20190706154011p:plain
pyvim, pymux

 
ref: Gallery — prompt_toolkit 2.0.9 documentation


 

- ptkの描画を理解する -

ptkのテキスト描画の根幹はprint_formatted_textにある。また、画面の管理、描画はApplicationクラスが担当しており、こちらも重要である。

print_formatted_textは、組み込み関数のprint関数と互換性をもたせながら、クロスプラットフォーム上で色やフォーマットを変更して出力する関数である。多くの機能がこの関数によって表示されている。

シンプルに利用するには、以下のように書く。

from __future__ import unicode_literals, print_function
from prompt_toolkit import print_formatted_text
from prompt_toolkit.formatted_text import FormattedText
from prompt_toolkit.styles import Style

text = FormattedText([
    ('class:aaa', 'Hello\n'),
    ('class:bbb', 'ITALIC\n'),
    ('class:hoge', 'under line\n'),
    ('class:piyo', 'This is bold.')
])

style = Style.from_dict({
    'aaa': '#ff0066',
    'bbb': '#44ff00 italic',
    'hoge': 'underline',
    'puyo': 'bold'
})

print_formatted_text(text, style=style)

f:id:vaaaaaanquish:20190706182334p:plain
print_formatted_text

上記のようなPythonらしい記法以外にも、HTML風の記法や、ANSI、Pygments tokenを利用した書き方ができ、print_formatted_textはそれらをよしなに変換してくれる。ptk 1.xまでは完全にPygmentsに依存していたが、2.xから幅広い記法をサポートしたため、2.xや3.xを使うと良い。

ref: Printing (and using) formatted text — prompt_toolkit 2.0.7 documentation

 
クロスプラットフォームに対応する場合や、Class nameについてより詳しく知りたい場合は以下Stylingのページ、もしくは実際のコードを確認する。
ref: More about styling — prompt_toolkit 2.0.7 documentation
ref: python-prompt-toolkit/defaults.py at master · prompt-toolkit/python-prompt-toolkit · GitHub


 

Application

Applicationクラスは、上記までで紹介したようなI/O、style、keybindingやwidgetを含めて動くアプリケーションを生成するためのクラスである。ptkで作成されたxonsh、pyvim、pymuxがそうであるように、ptkで定義されたツールを一つのCLIアプリケーションの形にする事ができる。

このApplicationクラスがScreenオブジェクトをレンダリングする過程を知る事で、ptkを利用したCLIツール開発について理解する事ができる。

 

Screenオブジェクトのレンダリング

ApplicationクラスはScreenクラスを生成し画面を描画する。描画に利用されるwrite_to_screenメソッドの動きを見る前に、用語をまとめておく。

  • Container
    • HSplit VSplit FloatContainerなど画面上の場所を確保してくれるやつ
    • 中でもWindowというよしなに色々やってくれる便利なアダプターが楽で便利
      • WindowはUIControlを中に埋め込めるのでよく使う
  • UIControl
    • UI周りの動的な情報をContainerに渡してくれるやつ
    • 以下のようなものがある
      • 編集可能でスクロール可能なバッファ表示のためのBufferControl
      • テキスト表示のためのFormattedTextControl
      • More about Control
  • widgets
    • UIの中に入れる物として抽象化された概念、簡単にレイアウトが作れる
    • TextArea, Button, Frame, VerticalLine
  • Layout
    • 上記らをラップすると折返しや表示をよしなにやってくれる
    • styleもここに入る

概念の多きで言うと以下のようになる
Application > Screen > Layout > Container, UIControl > widgets
それぞれ継承したものを、再帰的に利用する事もできる(Containerの中にContainer等)。中でもContainerオブジェクトとUIControlオブジェクトを組み合わせた時の相互作用を理解する事は、Applicationを作る上で非常に重要である。

 
Screenオブジェクトが持つRendererオブジェクトがContainerを特定の画面上にレンダリングする流れは、シンプルに書くと以下のようになる。

  1. Containerが持つwrite_to_screenメソッドが一定の大きさの長方形を生成
  2. UIContentがスペースを計算
  3. RendererがLayoutを参考にContainerを正しい位置に描画していく

ref: https://python-prompt-toolkit.readthedocs.io/en/2.0.9/pages/advanced_topics/rendering_flow.html

上記用語と流れを理解した上で、シンプルなApplicationを作成してみると分かりやすい。

from prompt_toolkit import Application
from prompt_toolkit.buffer import Buffer
from prompt_toolkit.layout.containers import VSplit, Window
from prompt_toolkit.layout.controls import BufferControl, FormattedTextControl
from prompt_toolkit.layout.layout import Layout

buffer1 = Buffer()
root_container = VSplit([
    Window(content=BufferControl(buffer=buffer1)),
    Window(width=1, char='|'),
    Window(content=FormattedTextControl(text='Hello world')),
])

layout = Layout(root_container)

app = Application(layout=layout, full_screen=True)
app.run()

Vsplit(縦に2分割するcontainer)の中にWindow(入出力可能な便利container)を3つ入れている。それをLayerにいれてApplicationとしている。結果はフルスクリーンで以下のように表示される。

f:id:vaaaaaanquish:20190706211619p:plain
左右に画面が分割され、左画面がテキスト入力可能なBufferControl、右画面がText表示になる様子

 
ref: Building full screen applications — prompt_toolkit 2.0.7 documentation
source: python-prompt-toolkit/application.py at master · prompt-toolkit/python-prompt-toolkit · GitHub


 

KeybindingとFilter

さらに画面の操作を円滑にするKeybindingsやbottom toolbarを設置する事もできる。また各動作や状態をhookして動かせるFilterクラスも存在する。
ここでは省略するが、各々設置する事ができるので把握しておくと良い。

keybindings: More about key bindings — prompt_toolkit 2.0.7 documentation
filter: Filters — prompt_toolkit 2.0.7 documentation


 

関連

私自身もptkへのコミット他、xonshなどの開発に寄与している。
その他、破壊的変更の多かったptk 1.xからptk 2.xへの移行のための記事を書いてあるので参考にできると思う。
vaaaaaanquish.hatenablog.com

フルスクリーンアプリケーションで雑に時計とかも作ったので参考に
f:id:vaaaaaanquish:20190706213406p:plain
github.com


おわりに

ptkは1.xから2.xへの破壊的変更こそ多かったものの、3.xに向けてpython 3.6から導入されたasync/awitを利用した非同期処理や、レンダラの強化、各クラスの相互作用の整理が進んでおり、非常に使いやすくなりつつある。

Pythonの資産が扱えるのが非常に大きく、MLエンジンを載せた補完やCLIと連動するWebアプリケーション等が出てくる事を期待している。