Stimulator

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

機械学習パイプライン構築を楽にするgokart-pipelinerを作った

- はじめに -

luigi、gokartで作ったtaskのパイプライン構築をちょっと楽にする(かもしれない)管理するためのツールを作った。

github.com

近年、MLOpsの一部である機械学習のためのパイプラインを構築するためのツールは飽和状態にあるけどそれらと比較してどうなのという話も書く。

gokart-pipelinerを使ってみる

gokartはエムスリー株式会社が開発している機械学習パイプラインOSSである。gokart自体、使った事がないし興味もないという人も居るかもしれないが、一度以下に提示するgokart-pipelinerの例を見てほしい。

# pip install gokart_pipeliner
import luigi
import gokart
from gokart_pipeliner import GokartPipeliner

# taskを定義する
class TaskA(gokart.TaskOnKart):
    def run(self):
        self.dump(['foo'])

class TaskB(gokart.TaskOnKart):
    before_task = gokart.TaskInstanceParameter()
    text = luigi.Parameter()

    def run(self):
        x = self.load('before_task')
        self.dump(x + [self.text])

# パラメータとパイプラインを書く
params = {'TaskB': {'text': 'bar'}}
pipeline = [TaskA, TaskB]

# run
gp = GokartPipeliner()
gp.run(pipeline, params)

luigiやgokartでは、1つのタスクを1classで表現する。TaskAは ["foo"] を保存するだけのタスク。TaskBはparameterに設定したbefore_taskを読み込んで、同じくparameterに設定した text を末尾に追加するタスク。GokartPipelinerがその2つのタスクをよしなに接続し、パラメータと共に実行している。

gokartの良さ

手前味噌にgokartの宣伝をするならば、この時以下のようなメリットがある

  • それぞれのTaskの以下データが別々にハッシュ値付きでpklファイルに保存される
    • self.dumpしたデータ
    • importした全てのmoduleのversion
    • Taskの処理時間
    • Task内で使われた全てのrandom_seed
    • 出力されるログ
    • Taskのクラス変数として設定された全てのparameterの値
  • TaskBのparameterを変えて実行した時は別のハッシュ値で上記ファイルが生成される
  • TaskAに新たにparameterを追加した場合はTaskBのハッシュ値も変わり依存関係を考慮して両方rerunされる
  • TaskA、TaskB間は上記の出力を中間ファイルとしてやり取りされるためメモリに優しい
  • dumpの出力ファイル形式を拡張できる(デフォルトでもcsv、zip、feather、png、…などをサポート)
  • 入出力時のpandas.DataFrameの型、columnチェック機能がある
  • 保存ファイルのディレクトリ構成がPythonスクリプトの構成から自動的に決まる
  • 基本的なnumpy、randomのシードは自動で固定化される
  • SOLID原則をなるべく守りながらコーディングできる
  • 保存された出力データとparameter、hashの管理はthunderboltなる別ライブラリでPython上で行える
  • 並列にタスクが動作してもredis経由でTaskがロックされる

機械学習モデリングにおいて、何をロードして、何を出力し、どのように繋げるか以外の殆どを自動的に決定し保存する仕組みになっている。また、その上でクラス単位でTaskを作る事によるソフトウェア開発における単一責任の原則などを守りやすくなっている。

近年は、機械学習パイプラインツールの戦国時代でもある。他の多種多様なライブラリと比較しても、モデリング時やproductionの再現性のための中間出力が多いし、Pythonコード上で見た時、デコレータや謎のメソッドがチェーンされまくったコードよりは保守性が高くなるはずだ(gokartを学ぶコストさえ払えば)。

例えば「pandas.DataFrameのtext columnから文字列の長さのcolumnを生成する」という処理は、無闇に大きな関数やスクリプトにせず、1つのファイルに1つのTaskとして考えて以下のように書いていく。

class CalcTextLengthTask(gokart.TaskOnKart):
    target = gokart.TaskInstanceParameter()
    __version = luigi.IntParameter(default=1)

    def run(self):
        df = self.load_data_frame('target', required_columns={'id', 'text'}, drop_columns=True)
        df['text_length'] = df['text'].str.len()
        self.dump(df[['id', 'text_length']])

gokartにおいては、1Taskの規模感さえ一致すれば、この書き方以外でコードを書くのは難しく、コードレビューや保守が行いやすい。また、このCalcTextLengthTaskの入力となるtarget taskを変える事で、hash値等のメリットを享受しながら使い回す事ができる。例えば機械学習モデルの汎用的なTrainタスクを書いておいてTaskInstanceParameterのみ変えるといった具合に。

また、parameterで中間ファイルのhash値が変わる事を利用して、__versionのようなパラメータを雑に付けてあげれば、「長さを測る前にstripしてからという処理に変更」した時にversion=2としてcommitしておくことで、あとからgitのlogをblameしたり、出力されるhash値付きのpklファイルを見比べる事でデバッグが行いやすくなる。


加えて、wrapしているluigiとの比較は以下のスライドを参考に。
gokartの運用と課題について - Speaker Deck
何もないluigiを書くよりも書きやすいと思えるはずである。

 

gokart-pipelinerの意義

gokartでモデリングしたり、コンペに出たり、会社での本番運用を重ねていく中でいくつか課題になってきた以下のような点を解決しようとしたのがgokart-pipelinerである。

  • パラメータとパイプラインが密結合しすぎ
  • パイプラインライブラリなのにやればやるほどrequiresメソッドが複雑になる
  • jupyter notebookと行き来するのがダルい
パラメータとパイプラインが密結合しすぎ

パラメータとTaskの動作が分離しているパイプラインは、近年の流行となりつつある。

特にFacebook社の公開したHydraはかなり大きな転機だったように感じる。yamlとデコレータを軸としたパラメータ管理で、yamlファイルさえ管理していればどんなパイプラインを書いても良いし、かなり管理も楽である。

一方でデコレータは闇魔法を生みやすいし、デコレートした謎の巨大な関数を見るのはツライので、もう少しコーディングに制約を持たせた形のパイプラインを作りたいと思っていた。(他人の書いたHydra+mlflowのコード見るのしんどすぎない?)

luigiにもconfigParserを使ったiniやyamlファイルを読んでパラメータとする機能はある。しかし、gokartにもTaskInstanceParameterというやつが居る。これ自体はtaskを依存関係の一部と捉えられる良い機構ではあるものの、Parameterという扱いとしてyamlのように一箇所で管理できないネックがあった。

gokart-pipelinerの場合を見てみる。

from gokart_pipeliner import GokartPipeliner
from ExampleTasks import *

pipeline = [TaskA, {'task_b': TaskB, 'task_c': TaskC}, TaskD]
params = {'TaskA': {'param1':0.1, 'param2': 'sample'}, 'TaskD': {'param1': 'foo'}}

gp = GokartPipeliner()
gp.run(predict, params=params)

pipelineは「Task同士のTaskInstanceParameterによる依存関係のみい」を表し、paramsは「各タスクのそれぞれのluigi.Parameter」を表していて、切り分けられている。元々のluigiのconfig形式にも対応しているので、configファイル、pipeline、paramsをそれぞれ考えつつ、この構成だけ保存しておけば一元管理もできる。

パイプラインライブラリなのにやればやるほどrequiresメソッドが複雑になる

gokartの機能としてrequiresというクラスメソッドがある。これは、読み込むデータを指定するメソッドで、requiresが返す値でluigiが依存タスクを決めている。

先程のタスクをgokart-pipelinerを考えず書いた場合は以下のようになる

class CalcTextLengthTask(gokart.TaskOnKart):
    target = gokart.TaskInstanceParameter()

    def requires(self):
        return self.target

    def run(self):
        df = self.load_data_frame('target', required_columns={'id', 'text'}, drop_columns=True)
        df['text_length'] = df['text'].str.len()
        self.dump(df[['id', 'text_length']])

このrequiresは以下のようにlistやdictを返したりもできる。

    def requires(self):
        return {'target': self.target, 'model': self.clone(MakeModelTask)}

更に複雑に、依存関係やパラメータ、分岐を書く事もできる。

    def requires(self):
        data = TrainTestSplit(data=MakeData(path='/'), split)
        if self.parameter_a == 'var':
            task = MakeModelTask(data=data, param_a=0.1, param_b='foo')
        else:
            task= MakeModelTask(data=self.data)
        return {
            'data': data,
            'model': self.clone(task)}

この状態では、コンペのような実験とコーディングを繰り返す時に、依存関係がどうなってるか把握するのがどんどんしんどくなる。gokartに依存関係Treeを出力する機能があるが、流石にしんどい。こういった複雑なrequiresを集約するエンドポイントになるTaskを作ったりするが、次はそのエンドポイントからしか全体が実行できなくなったりしていくし、エンドポイントが増えれば増えるほど、どのファイルを見て回ればいいか分からなくなる。その上で途中にTaskを挿入したいとなったら、と考えるとただただ辛くなる。

なので、そもそもrequiresを書かない制約を付ければ良い。


例えば、gokart-pipelineでの先程の例を考える。

pipeline = [TaskA, {'task_b': TaskB, 'task_c': TaskC}, TaskD]
params = {'TaskA': {'param1':0.1, 'param2': 'sample'}, 'TaskD': {'param1': 'foo'}}

ここでTaskDは、リストの1つ前のdictを引数にするように、以下のように書いたクラスである。

class TaskD(gokart.TaskOnKart):
    task_b = gokart.TaskInstanceParameter()
    task_c = gokart.TaskInstanceParameter()
    param1 = luigi.Parameter()

    def run(self):
        b = self.load('task_b')    # list
        c = self.load('task_c')    # list
        data = b + c + [self.param1]
        self.dump(data)

requiresメソッドは、TaskInstanceParameterのパラメータ名から、gokart-pipelineが生成する。この制約によって複雑なrequiresを書かれる事もなく、pipelineのlistだけを変数やdictを使って上手く書いてやれば良いだけになる。

jupyter notebookと行き来するのがダルい

gokart自体をjupyter notebookで動かす方法はあるものの、かなりハックじみた方法となる*1

gokart-pipelinerはjupyter notebook上で動く。
gokart-pipeliner/Example.ipynb at main · vaaaaanquish/gokart-pipeliner · GitHub

Task同士は中間ファイルでやり取りされるのでメモリをバカ食いしないし、classである事さえ意識して一般的なソフトウェア開発の心得に沿って書けば、ここで書いたコードをそのままproductionコードにするのも容易になる。

もちろんタスクを動かした後、thunderboltを使って出力ファイルをメモリに読み込むなどして、jupyter上で触っても良い。
github.com

future work

今後上手く使えてきたらできそうなこと

  • pipelineのリストの書き方のベストプラクティスを探る
  • runの返り値として出力データを返したりできるようにする
  • pipelineを決めたら並列化できる所を自動で並列に動作させたりする
  • jupyter notebookからも別のプロセスとして動かす(Taskが動く最中もnotebookが実行できる)

おわりに

試しに作ってみた段階なので、これからコンペに出たり本番運用に使ってみたりして調整していきたい。

gokartは良いぞという話を散々書いたが、gokartは中間ファイルを経由するだけにDataFrameを良く使うテーブルデータでは使いやすく、画像コンペのような所では全然良さを発揮できなかったりする。いやいや画像音声テーブルなんでも自分のツールは共通化しておきたいわ、という人に不向きという所をなんとか改善できればと思ってはいる。

なんとなく形になったら、gokartにマージしてもらって、gokartの定番となっても良い気もする。

がんばろう。

 

*1:機械学習プロジェクト向けPipelineライブラリgokartを用いた開発と運用 - エムスリーテックブログ https://www.m3tech.blog/entry/2019/09/30/120229 を参照すると良い

「実用的でないPythonプログラミング」がよかった

はじめに

2020/8/12に発売されたImpractical Python Projects: Playful Programming Activities to Make You Smarterの日本語訳書である、「実用的でないPythonプログラミング」をひょんな事から献本していただく事になった。(訳者が同僚である)



ありがちなプログラミング初学者向けの本から1段上がった中級者向けの良い本だと感じたので、当ブログでたまにやっている筆者、訳者に媚びを売るシリーズの一貫として、感想を記す。


 

書籍の概要

実用的でないPythonプログラミング」は、想定する中級レベルのアルゴリズムの問題を例に取り、Pythonでの美しいコードの書き方や、コンピュータサイエンスにおける基礎知識、オブジェクト指向プログラミングの必要性を説いていく形で執筆されている。


以下は、章ごとに出される問題とそこでのキーワードをまとめてみたものだ。パッと見渡してもコンピュータサイエンスの基礎知識をなぞっているのが、よくわかる。

1.【乱数による名前生成】Pylint、DocString
2.【回文の検出】semordnilap、cProfile、ハッシュテーブル、再帰
3.【アナグラムを解く】Sort、collections、モジュールの分割、トライグラム、バイグラム
4.【暗号解読】ルート転置式暗号、レールフェンス暗号、総当たり攻撃と探索
5.【暗号解読と生成】null暗号、語彙のリストと安全性
6.【MS Wordの取扱】OSの差異、フォント、カーニング、トラッキングpython-docx、ヴィジュネル暗号
7. 【良いネズミの育成、金庫破り】組み合わせ最適化遺伝的アルゴリズム
8. 【俳句の音節の検出】自然言語処理コーパス、CMUdict、nltk
9.【俳句の生成】マルコフ連鎖デバッグ、スキャフォールド、logging、チューリングテスト
10.【銀河系のモデリングフェルミパラドックス多項式、ドレイクの方程式、グラフィカルモデル、ユークリッド距離、NumPy、SciPy、tkinter
11.【モンティ・ホール問題】モンテカルロシミュレーションオブジェクト指向プログラミング、tkinter
12.【老後資金シミュレータ作成】確率的サンプリング、モンテカルロシミュレーション実応用
13.【火山活動シミュレーション】pygameOpenGL、math、ラジアン、三角法、ベクトル
14.【万有引力ケプラーの法則のシミュレーション】複雑な問題のソースコードへの落とし込み、ゲーム作成
15.【天体画像の精細化】画像処理、画像補正、スタッキング、pillow
16.【不正検知】ベンフォードの法則、matplotlib

 
実際に上記のような問題に当たる上でも、ユニークな問題の与えられ方をする書籍でもある。

例えば、暗号の章では実際の南北戦争時代などのエピソードを例題として扱っていたり、回文ではハリーポッターを引用している(酷いネタバレでもある)。モンティ・ホールゲームの図解なども、複雑な問題を受け止めやすいような図解があり、優しい大学の講義を彷彿とさせる。

f:id:vaaaaaanquish:20200830175434j:plain:w400
モンティ・ホールゲームに関する例題の図

ユニークな問題に対して、解答となるソースコードが全て公開されている点も大きく評価できる。
中級者向けの問題だけあって、実際に問題を解く場合にはそれなりの量のコーディングが必要となるが、解答とそのコードの解説を数行単位で行ってくれており、最悪自分でPythonを書かなくても理解に及ぶだろう。

アルゴリズムについても図解や数式の解説が散りばめられており、例題と合わせて利用することで、前述したキーワードに関連する内容がスッと入ってくるようになっていると感じた。

f:id:vaaaaaanquish:20200830175438j:plain:w400
回文に関するアルゴリズムの図解

これらを課題のように進める中で、計算量の肥大化、コーディングにおけるオブジェクト指向の重要性、インターフェースなどを意識させる形にもなっており、最後の14〜16章あたりにおいては、それなりに大きなGUI付きのプログラムを作成できるようになっている。
この進め方も、より一般的なコンピュータサイエンスの講義に近いものを感じる。

 
また、何より素晴らしいのは、章ごとに書籍以上の情報を参照したい場合のURLが書かれている点だ。

f:id:vaaaaaanquish:20200830180516j:plain:w400
さらに読むなら

引用に加えて、専門書以外でこれが出来ている書籍は多くはない。様々なCSの問題に対して興味を持つための足がかりにもなるだろう。


 

どういう人がターゲットか

前述した、問題とキーワードに対して感じる内容は、概ね「バックグラウンドとしてコンピュータサイエンスに近い学科を卒業しているか」に依る印象がある。

私自身は高専から大学院までCSの学科に所属していた事もあり、上記キーワードに対して「大学1~3年程度で演習に出てくるワード一覧である」と読みながら感じた。情報工学科などを出た大学生であれば、記憶にうっすらある状態であろうとも、調べて思い出しながらであればスラスラ問題を解く事ができるのではないだろうか。

CS出身者はある意味ターゲット層には遠いかもしれない。
昔懐かしい問題演習として購入してみる形であれば、面白い題材が詰まった良い本であるとも言える。

 
レベル感としては、競技プログラミングより少し応用を意識している(ユーザインタフェースの作成、オブジェクト指向への誘導など)。
一方で問題を汲み取り、アルゴリズムソースコードに落とし込むことを鍛える側面もあり、競技プログラミングに入る前後で力を付けるために読んでも良さそうである。

 
CS出身者でない方には非常に読んで実践してみて欲しい書籍ではある。
ただし、大学の1年分の講義を集約したような書籍ではあるので、時間がかかる事を覚悟してトレーニングとしておすすめしたい。加えて、初学者向けの書籍とは違って、1単語1単語丁寧に解説してくれている訳ではないので、ある程度未知の単語を調べながら、知識欲に従って楽しく進めると良さそうである。

本書籍を読むことで、アルゴリズムに関連するトレーニングができるだけでなく、新しい知識への取っ掛かりも得られるはずである。
あと、CS出身者の謎のあるある会話に入り込めたりするメリットもありそうだなと感じた。

 

おわりに

実用的でないPythonプログラミング」のタイトルにもある通り、例えばWebサービスを構築するエンジニアが日常的にこの書籍の知識を使うかと言われたら「実用的ではない」と答えるだろう。近年では、ソフトウェアエンジニアも多様化してきているので、ある側面で実用的でないのは確かである。

一方で、コンピュータサイエンスが実用的でないかどうかに対する答えは、訳者が「実用的でないと言いつつ実用的な本になった」とあとがきで語っている通り、プログラミングを行う上で基礎知識として必要なものにもなりうる。
 
私個人としては、Python初学者向けの本を読んだ後、基礎を固める上で重要な項目が散りばめられた書籍でもあると思うし、多くのソフトウェアエンジニアが学んでおくべき内容である、とも思う。現に近年では、IT企業においてこのようなコンピュータサイエンスに関連したコーディング試験を面接で実施するのは一般的になっているし、自分が所属する企業でも、バックグラウンドに関係なく本書籍に乗っているようなレベルの問題を解く力は必須になっていると感じる。


こういったコンピュータサイエンスを書籍の中でも、問題に寄ったドリルのような書籍である。

 
ソフトウェアエンジニアの皆様には、是非ともベースラインを高める意味でも、トレーニングの意味でも実際に手に取って読んで問題に挑んでみる事をおすすめしたい。

 


 

poetryを利用した動的なバージョン管理とGitHub ActionsによるPyPIへのrelease

はじめに

この記事を読んで出来る事

  • poetryによる外部モジュールバージョン管理
  • poetry-dynamic-versioningによる動的なバージョン付与
  • GitHub Actionsを利用したPython周りの基本的なCI/CD設定
  • GitHubのReleaseタグ付与をTriggerとしたPyPIへのアップロード

今後私がPythonで何かライブラリ作ろうと思ったらこれを実施するぞというメモです

 

 

poetryによるモジュールバージョン管理

バージョンをGitHubのタグで管理したい事の方が多いはず。
setup.pyを利用する場合は、一般的にsetuptools_scmを使うが、poetryはsetup.pyのようにbuild時にスクリプトを組み込むのは基本できないので、poetryの実装にpatchを当てる形で動的にバージョンを取得する。そのためのライブラリが以下のpoetry-dynamic-versiong。patchを当てている箇所はここ
github.com

git repository以外にもsubversionに対応していたり、独自のバージョンフォーマットも扱えるので、基本的にはこちらで問題なさそう。

 
pyproject.tomlを書いていく。

[tool.poetry]
name = "module_name"
version = "0.0.0"  # using poetry-dynamic-versioning
description = "sample tool"
authors = ["vaaaaanquish <6syun9@gmail.com>"]
license = "MIT"
readme = "README.md"
homepage = "https://github.com/vaaaaanquish/module_nam"
repository = "https://github.com/vaaaaanquish/module_nam"
documentation = ""

[tool.poetry-dynamic-versioning]
enable = true
style = "pep440"

[tool.poetry.dependencies]
python = "^3.7"
pandas = "^0.25.0"
matplotlib = "*"

[tool.poetry.scripts]
my-script = 'module_name:main'

[build-system]
requires = ["poetry"]
build-backend = "poetry.masonry.api"

 
githubのrelease tagで 「v0.0.1」 というフォーマットを採用していれば、以下を追記するだけで良い

[tool.poetry-dynamic-versioning]
enable = true
style = "pep440"

tool.poetry配下のversionが残る事だけが気がかりだが、「0.0.0」のようにしておけば問題にはならなそう。現状外すとpoetry build時にエラーとなる。

「v0.0.0」以外のformatを使っている場合は、READMEのConfigurationを参考にpattern、もしくはformatを設定する。

tag v0.0.2が打たれたrelease
tag v0.0.2が打たれたrelease

 
poetry-dynamic-versiongを使う場合には、poetryのdependenciesとしてではなく、poetryと同じレイヤーのPythonでinstallする必要がある。

pip install poetry
pip install poetry-dynamic-versioning

# poetry install
poetry update
poetry build

出力は以下のようになり、github release tagと連動してmodule生成が出来ている事が確認できる。

Building module_name (0.0.2)
 - Building sdist
 - Built module_name-0.0.2.tar.gz

 - Building wheel
 - Built module_name-0.0.2.whl


 

PyPIへのアップロード

手元からPyPIにpushするにはpoetry publishを使う方法が一般的。
以下のような形でpublishすれば良い。

poetry publish --build --username {hoge} --password {piyo}

基本的にはあまり手元からやりたくないので、GitHubにmasterマージされたりしたタイミングで自動でPyPIにアップロードしてもらいたい。後述する。


 

GitHab Actionsを用いたCI/CD

GitHub Actionsの設定ファイルを書いて、release tagをトリガーにして動的にアップロードするようにする。

# This workflows will upload a Python Package using Twine when a release is created
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries

name: Upload Python Package

on:
  release:
    types: [created]

jobs:
  deploy:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Set up Python
      uses: actions/setup-python@v1
      with:
        python-version: '3.x'
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install poetry poetry-dynamic-versioning twine
    - name: Build and publish
      env:
        TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
        TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
      run: |
        poetry publish --build --username $TWINE_USERNAME --password $TWINE_PASSWORD

releaseタグが打たれた事をトリガーにしてmasterのコードをPyPIにpushするやつ。.github/workflows/python_publish.ymlにしてrepositoryに含める。
repo内の Settings > Secrets からユーザ名とパスワードを追加する。

repo内の Settings &gt; Secrets からユーザ名とパスワードを追加する。
repo内の Settings > Secrets からユーザ名とパスワードを追加する。

 

その他GitHubでやること

flake8、yapfなりのフォーマッタのCIを別途作る。
Settings > Branch protection rule からCIの必須化とmasterにpushできない設定をしておく。

Branch protection rule
Branch protection rule


PRからGitHub Actionsのbuildタスクが通らないとmergeできない事を確認する。

PRからGitHub Actionsのbuildタスクを確認
PRからGitHub Actionsのbuildタスクを確認

これで一連の設定はおわり。

setup.cfgやsetup.py、pipfileで書いていたものがほぼ不要になるし、PyPIのREADMEが勝手にMarkdownレンダリングされたものに変わっていたり、本当にやることが減って何かと便利。pipenvより早い、PEP 518 で規格化されている。良い感じ。
 

機械学習モデリングの広辞苑的書籍「Kaggleで勝つデータ分析の技術」が良かったので筆者に媚を売る

- はじめに -

当ブログでは恒例になっている、献本されたので筆者に媚を売るシリーズです。

今回は10/9に発売予定の「Kaggleで勝つデータ分析の技術」という書籍なんですが、既に発売前にしてAmazonベストセラー1位。豪華著者陣とKaggleにおいては日本有数の起業と言っても過言ではない、DeNA株式会社の豪華レビュワー。筆者がブログを書いていたりu++さんがめちゃくちゃ丁寧な書評を書いていたり、Kaggle Grand Master各位の薦めツイートも出てきた段階で、もう私が媚を売る必要すらないと思いますが、良かったので感想だけでも残しておければと思います。

Kaggleで勝つデータ分析の技術

Kaggleで勝つデータ分析の技術

端的に言えば、テーブルデータにおける機械学習モデリング、データ分析の広辞苑+αな書籍で、筆者らがブログやSNS等で述べている通り「暗黙知を洗い出す」ような良書でした。

 

 

- どんな内容だったか -

前置きとして「広辞苑的な書籍」と表現した通り、Kaggle、データ分析における重要なワードとその適切な範囲までの解説がセットで広く書かれた書籍です。

詳細なアルゴリズムや数理的な背景までは説明していませんが、その分幅広く適切な深さの解説、コラム、ソースコードや過去コンテストの実例がついています。

f:id:vaaaaaanquish:20191005205334j:plain:w400
数式を最小限にしつつも実例を交えて各評価指標やアルゴリズムを紹介

コードや背景については分量は少なめですが、私が読んだ技術書の中でもズバ抜けて参考文献を非常に丁寧にまとめており、気になる所が後追い出来るようになっているだけでなく、ソースコードGitHubで公開されるとの事なので、基本的に読んだ後に手を動かして体感する事も可能そうです。


また、「決定木の気持ち」「過去コンペで話題になった手法」といったKaggler同士の中でのミームについても解説が入っています。Kaggle自体のコミュニティ性から言っても、技術情報や共通のミーム、周囲の戦略の収集は、コンテストに挑むにあたって重要な事でもあるので、今からでも書籍を読んで把握しておいて損はないでしょう。

f:id:vaaaaaanquish:20191005210157j:plain:w400
基本的なコンテストでの分析フロー等が図付きで解説されている

 
私自身、機械学習やデータ分析のブログやSNS投稿、スライド、発表や同人誌などで情報収集をする事は多々ありますが、「あなたの言ってる定義微妙に違ってない…?」と思う事は少なくありません(私が毎回正解を言っている確信がある訳でもありません)。そういった機械学習モデルや統計モデルを活用するコンテストやプロジェクトが一般的に広まってきたという段階で、各ワードに対する認識の共通化を図れる良い書籍です。

この一冊とGoogle翻訳があれば、Kaggle上のDiscussionやKagger Slack、オフラインイベントでの会話をかなり的確に把握できるようになるとも感じます。そういった場で、今まで曖昧に流してきたワードがあるような方にオススメの一冊です。

 
書籍のまえがきには、「ビジネス的な側面について触れない」とも書かれており、事実内容もその通りではありましたが、弊社の機械学習チームではこの書籍に出てくるような単語を前提の共通ワード、コンテキストとして扱う場面が多くあるなと読んでいて思いましたし、コンペに出る事を目的としない場合でも自力を上げる知識が多く含まれています

 
個人的には、これほど「タスクの評価」「モデルの評価」「Validationの設計」について広く用語がまとまった書籍は他に見たことがありませんし、実務でモデルの評価について考えている人はここに書いてある事は前提として一通り把握しておくべきだとも思います。実務だとここに事業的な評価やシステムの話が入ってくると思いますが、まず前提として知っておかないとモデルも構築できないと思います。

 
私自身、これを読みながら以下のようなツイートをしており、書籍と公開されるGithubを合わせれば、自然言語処理100本ノック、画像処理100本ノックに続く、手を動かして機械学習を広く知るコンテンツの1つともなりそうです。


知の高速道路が整備されるのは良いことです。

 

- 読んだらKaggleで勝てるか -

読んだだけでは勝てそうにありません。

 
この書籍は広辞苑のように大きく知識を広げ、ともすればコンテスト中に逆引きできる良書籍ですが、実際には多くの知見、バックグラウンドがその背景に詰め込まれた書籍でもあります。

例えば、「基本的なライブラリ(numpy, pandas, sklearn, ..., etc)の使い方」から「予測確率」「次元削減」といったワード、果ては「教師あり、なし学習」辺りまで、機械学習、統計の教科書的な知識は前提知識として必要としてきます。プラスでモデルの背景が分かっていないと「ん?」となる部分もいくつかあります。これは、それらがきちんとしている著者らが書いたからこそ正確かつ客観性、再現性のある書籍になっているという話でもあります。「舟を編む」ではないですが、これ程の機械学習、データ分析の範囲を適切な表現と深さで書いている本はほとんどありませんし、流石と言わんばかりです。
 
そういった事もあって、前提知識がないと出来ない事、例えば「アルゴリズムわからないので機械学習モデルどれ選べばいいか分からない」「可視化の結果どの方向性に行くのが正しいか分からない」という課題を解決してくれる書籍では無いので、「これでKaggle始めよう!」という人には前提として他にもいくつかの書籍やブログと一緒に読んでいく必要があるかなと思いました


全体が薄い訳ではなく、「xgboostのアルゴリズム、チューニング」「ベイズ最適化」については大きく3~5ページが取られています。筆者としてもKaggleで重要なBoostingとチューニングに限っては丁寧に解説したかったのだとも思います。特に近年では業界内でも有名なブログや企業、学術機関からも、不要なパラメータのチューニングやLeakageを代表に、コンテストでは一般的にバッドプラクティスとされる話が多く出回るようになりましたし、注力して書かれていると感じます。

逆に言うと、このxgboostの丁寧な解説と同量またはそれ以上のバックグラウンドが、他全ての章に書かれた技術についても本来ある訳でもあります。この書籍から分からなかった単語をググって、勉強して、コンテストに出てという形を想定した足掛かり的な書籍でもあると感じました。

 
当たり前ですが、実際に手を動かして、体感して、コンテストに時間を捧げてsubmitしない事には勝てないので、やっていきの気持ちが大事です。というか知ってるだけで勝てるなら私だってk…

 

- どんな書籍と読むと良さそうか -

基本的には、書籍内の参考文献が非常に優れておりその中のスライドやブログ、書籍を参考にすると良いと思います。実際この付録に書籍の3割くらいの価値が詰まっていると思います。

実際この付録のURL一覧だけでGitHubスター100はあげたいレベルです。

 
中でも、Kaggleでプラスになると考えて抜粋すると、「特徴量エンジニアリング」辺り。「データ解析のための統計モデリング入門」は一緒に買っておくと間違いないと思います。

機械学習のための特徴量エンジニアリング ―その原理とPythonによる実践 (オライリー・ジャパン)

機械学習のための特徴量エンジニアリング ―その原理とPythonによる実践 (オライリー・ジャパン)

はじパタやプロフェッショナルシリーズも悪くないですが、上の2つは直接的にKaggleや分析に効くいい本だと思います。
 
参考文献外だと「仕事ではじめる機械学習」「機械学習のエッセンス」はこの本の支えになる知識が多いと感じました。持ってなければ是非一緒に。

仕事ではじめる機械学習

仕事ではじめる機械学習

 
最近は書籍も多い(献本も多い)ですが、低いレベルのまとめ的な書籍も少なくないです。そういう意味でも「Kaggleで勝つデータ分析の技術」の厳選された参考文献と過去コンテスト情報は読んでおくべきだと思います。


 

- おわりに -

 
数年前、偶然同僚にKaggle Grand Masterが居たので気になってKaggleを始めましたが、まさかこんな良質で立派な書籍が出たりする程大きくなるとは…正直想像していませんでした。しかも、Expertになってからボーッとしてたら、数年で幾人もの人に追い抜かれ、何も言えない人になってしまいました。悔しいです。

DeNA株式会社に蔓延する攻撃的なSNSアカウントから「Expertが書いてる書評だから無意味」と言われそうなので、ブログはこの辺にしておきます。

 
媚を売られる側になれるよう、私も頑張ります。


Kaggleで勝つデータ分析の技術

Kaggleで勝つデータ分析の技術

 

追記:2019/10/05
らしいです。これは失礼しました。

xonshのEnvironment Variablesの全て

- はじめに -

以下の記事でxonshのEnvironment Variablesの大体の日本語訳を書いた。

xonshrcを書く - Stimulator

しかし、上記の記事は2017年次の物かつ、要約的な記事のため、より詳細に見て開発に以降できるよう本体へのコードや掴みどころをメモした記事を書いておく。

本家ドキュメントは下記なので参照のこと。

xon.sh
 

 
 

- Windowsに関連するもの -

Windowsの人はまずここ見て設定。
xon.sh

$ANSICON

Windowsのcmd.exe(コマンドプロンプト)において、ANSI escape sequencesを表示するためのアプリケーションであるansicon*1を利用するかどうかの設定。ansiconを利用する場合にTrueにしておくと、$TITLEをプロンプトのタイトルに設定するようになる。実装でいうと以下のような感じ。

import ctypes
ctypes.windll.kernel32.SetConsoleTitleW(env.get("TITLE"))

 

$INTENSIFY_COLORS_ON_WIN

Windowsのcmd.exeを利用する時に、colorを見やすくするかどうかのフラグ。青色がシアンに置き換わったりする。
そもそも近年でcolor style自体がかなり柔軟になったので、不要かも。細かく指定しない場合には使える。

 

$WIN_UNICODE_CONSOLE

Trueであれば、WindowsでのUnicodeサポートを有効にする。win_unicode_consoleなる外部ライブラリをimportしているだけ。以下必須。

pip install win_unicode_console

 

$PATHEXT

環境変数のPATHを見に行った時に、実行可能ファイルとして判定する拡張子のリスト。
".EXE"のように大文字で指定する。xonsh上でWINDOWS判定されていれば、[".COM", ".EXE", ".BAT", ".CMD"]がデフォルトで入るので、それ以外に必要であれば追加する。

 

- xonsh本体の動作に関するもの -

$XONSHRC

rcファイルへのpathのリスト。['~/xonshrc', '~/.config/xonsh/rc.xsh']のように複数あれば前方から複数読み込まれるし、ファイルがなければ読み込まれず終わる。

$VIRTUAL_ENV

アクティブなPythonへのPath。pythonやpipコマンドが指すところでもあり、xonsh用のpythonバージョン管理ツールであるvoxも参照するpathになる。
デフォルトでは変数が設定されていない状態なので注意。

 

$XONSH_HISTORY_SIZE

historyのサイズの指定。「(8128, "commands")」「"8128 commands"」のようなsetか文字列で、保存するコマンド数(commands)、保持する履歴ファイル数(files)、許可される秒数(s)、バイト数(b)の単位のどれかと値を指定する。

$XONSH_HISTORY_BACKEND

historyの保存形式。デフォルトはjsonだが、sqlを設定すればsqliteをバックエンドに選べる。設定は以下を見ると良い。
https://xon.sh/tutorial_history_backend.html

 

$SUPPRESS_BRANCH_TIMEOUT_MESSAGE

Trueであれば、VC_BRANCH_TIMEOUTに設定された時間を過ぎてもPROMPTの文字列、branch名、色の生成が終わって無かった場合に強制的にタイムアウトとしメッセージを表示する。PROMPTに{curr_branch}でブランチ名を表示させようとするが、git repoが巨大で…という時に起こる。

 

$VC_BRANCH_TIMEOUT

gitのブランチ名などをPROMPTに表示する時にタイムアウトする秒数。デカいrepo触ったりする時に設定しておくと良い。

 

$VC_HG_SHOW_BRANCH

Trueであれば、Mercurialのブランチを表示する。gitでなくMercurial使っている場合はこちら。

 

$UPDATE_OS_ENVIRON

Trueであれば、xonshのEnvが追加、削除された場合にその変更をos側の環境変数にも加える。デフォルトはfalse。
中身は「os.environ.update」を少し工夫して呼んでいるだけなので、個別にos側に追加したい時は不要。WindowsとかOSの環境変数が影響の大きい環境では使える場合があるかも。

 

$VI_MODE

Trueであれば、シェルの操作がvi風になる。iでinsert、escでnormalモードになるなど。
実装自体はprompt toolkitの機能で、現行のモードを判定するfilterとして実装されている以下の部分から追っていくと良い。
https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/prompt_toolkit/filters/cli.py

 

$XDG_DATA_HOME

英語では「Open desktop standard data home dir」となっているが、普通にセッションログやスクリプトの実行ログが入る場所へのpathというだけ。以下のXONSH_DATA_DIRに近いが、こちらのPathはxonsh用ではなく、一般的な環境全体用。
すこし踏み入ればpygmentsを使った実装になっていることが分かるが、難しい事はほとんどしておらず整備もあまりされていない印象。pygmentsすごい。

 

$XONSH_DATA_DIR

セッションログやスクリプトの実行ログが入る場所へのpath。
デフォルトでは「$XDG_DATA_HOME + "/xonsh"」 が設定されている。

 

$XONSH_CACHE_EVERYTHING

Trueであれば、全てのコード(コマンド含め)をキャッシュに保存する。
ここで言っているキャッシュというのは、historyではなく、そのセッションにおけるキャッシュで、できるだけコンパイルして保存しておいて次回のコマンド実行を早めるというもの。

 

$XONSH_CACHE_SCRIPTS

Trueであれば、スクリプト実行をキャッシュに保存する。
機構自体は上記のXONSH_CACHE_EVERYTHINGと同じだが、こちらはexecを通るようなスクリプトを実行した場合のキャッシュ。

 

$XONSH_TRACEBACK_LOGFILE

XONSH_SHOW_TRACEBACKがTrueの場合に、ログファイルとして残す先。ファイル名か不要の場合はNoneにしておく。

 

$XONSH_STORE_*

Trueであればhistoryに標準入出力を保存する。

$XONSH_STORE_STDIN

xonshの!()や![]オペレータを使って実行されたものをhistoryに保存する。

$XONSH_STORE_STDOUT

stderrやstdoutをhistoryに保存する。

$XONSH_DATETIME_FORMAT

ログやhistoryなど多くの場所で使われるdatetimeのフォーマット。デフォルトでは「"%Y-%m-%d %H:%M"」。
利用先は多いが、実装はtools.pyの中に収まっているだけでシンプル。

 

$XONSH_DEBUG

デバッグモードの指定。1であればimport情報、2であれば入力変換、コマンド置換の情報、3以上であればPLY解析メッセージと情報が増えていく。
よくissueを立てると「$XONSH_DEBUG=3 で一回ログ出して見て」とよく言われたりする。
実装自体は、参照されている部分が多いので変更は大変そうだが、追加はlogging足すだけなので参考に。

 

$XONSH_ENCODING

xonshのサブプロセスで利用されるエンコーディング。デフォルトでは「sys.getdefaultencoding()」が入る。
I/O制御で使われているTeeクラス、入力を作るreadline辺りが主に利用している。

 

$XONSH_ENCODING_ERRORS

Pythonエンコーディングエラーが出た時の処理。多くの場所で共通して使われている。
入る値は以下を参考に。
https://docs.python.org/3/library/codecs.html#error-handlers

 

$XONSH_PROC_FREQUENCY

連続したパイプラインを実行する時、キューを読み込むためにxonshプロセススレッドがスリープする秒数。
スリープタイムというよりは、コマンドを連続で実行した時、スレッド同士のキューが詰まった場合にタイムアウトする時間のイメージ。

 

- コマンド補完に関するもの -

$UPDATE_COMPLETIONS_ON_KEYPRESS

Trueであれば、キー入力時に毎回補完候補を出すようになる。例えば補完であればTABを押さなくてもキー入力毎に候補を表示できるようになる。
PROMPTの表示を評価するUPDATE_PROMPT_ON_KEYPRESSも別にあるので混同に注意。

f:id:vaaaaaanquish:20180622142703g:plain
キー入力時評価の例

実装自体はprompt toolkitの機能で、xonshはこの変数をフラグとしてptkの引数となるcomplete_while_typingに投げているだけである。その仕組み自体を把握するにはptkを追う必要がある。詳細は以下。
https://python-prompt-toolkit.readthedocs.io/en/latest/pages/asking_for_input.html?highlight=complete_while_typing#complete-while-typing

 

$AUTO_SUGGEST

Trueの時にfish shellのようなグレーアウトの補完候補を出す*2

f:id:vaaaaaanquish:20190824001450p:plain
AUTO_SUGGEST

この機能はxonshのコアライブラリであるpython prompt toolkit内の実装なので、そちらを参考にすると良い。
https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/docs/pages/asking_for_input.rst#auto-suggestion

表示されている補完候補を確定するときは「右矢印」「ctrl+e」がデフォルトで設定されている。変更する場合は以下が参考になる。
xonsh[ptk]で、Suggestionを確定するキーバインドを設定する - Qiita

下記のAUTO_SUGGEST_IN_COMPLETIONSとも関連しており、両者がTrueになっている場合、AUTO_SUGGESTで表示される候補をTab補完から除く等の機能があるので設定はよしなに。

 

$AUTO_SUGGEST_IN_COMPLETIONS

Trueの時、Tabキーで補完候補を表示する。$UPDATE_COMPLETIONS_ON_KEYPRESSがTrueの時は、Tab以外のキーでもキー入力時に補完候補が動的に表示される。

AUTO_SUGGEST同様、xonshというよりはpython prompt toolkitの機能であり、そちらを呼び出すかどうかのフラグにあたる。
https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/docs/pages/asking_for_input.rst#autocompletion

 

$COMPLETIONS_DISPLAY

Pythonのコードの補完を表示するか、もしくは表示形式をどうするか。

none or false

表示しない。stringの"none"や"false"でも良いし、PythonのNoneでも、boolのFalseでも良い。

single

1列で補完候補を表示する。stringで"single"。

f:id:vaaaaaanquish:20190824214546p:plain
$COMPLETIONS_DISPLAY = "single"

multi or true

複数列で表示する。デフォルト値。stringで"multi"、"true"とするか、boolのTrueを指定する。

f:id:vaaaaaanquish:20190824214823p:plain
$COMPLETIONS_DISPLAY = "multi"

readline

GNU Readlineの動作を再現した表示にする。stringの"readline"。

f:id:vaaaaaanquish:20190824214905p:plain
$COMPLETIONS_DISPLAY = "readline"


引数の型が柔軟なのは歴史的経緯。こちらはPythonの補完のみでbashの補完はこのEnvに依存していないので注意。

 

$COMPLETIONS_MENU_ROWS

補完を出す行数の指定。stringの"multi"だと5行で横幅最大まで補完を省略して表示する。int値を指定しても良い。

 

$COMPLETION_IN_THREAD

補完をasync/awaitで表示する。
実装自体はprompt toolkitのAppの引数にフラグを入れているだけで、非同期補完の詳細を見るにはptkのasync_promptの実装を見ると良い。
https://python-prompt-toolkit.readthedocs.io/en/master/pages/asking_for_input.html#asynchronous-completion
https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/prompt_toolkit/shortcuts/prompt.py#L909

 

$COMPLETION_QUERY_LIMIT

補完メニューの表示件数。最大件数では無く、これを超えたら補完が表示される閾値のイメージ。
xonshやprompt toolkitの機能というよりは、GNU Readlineのrl_completion_query_itemsをglobal変数として入れておくという実装なので、詳しくは以下を読む。
GNU Readline Library - Programming with GNU Readline

 

$COMPLETIONS_CONFIRM

タブ補完メニューが表示されている時、FalseだとEnterでコマンドを直接実行、TrueだとEnterでコマンドを確定のみして確認状態にする。
実装はprompt toolkitのkeybindにfilterを設定しているので、理解したければptkのkeybindingsを理解してからが良い。

 

$BASH_COMPLETIONS

bashのcompletionを利用するために、bash_completionスクリプトへのPATHを設定するための変数。sshの補完やgitの補完が含まれるため、基本的には必須だと思う。
listかtupleで最初に有効だったものが利用される。

例えばMacではbrewbashの補完が導入できる。

brew install bash-completion2

こちらは、"/usr/local/share/bash-completion/bash_completion" にファイルが配置される。デフォルトでBASH_COMPLETIONSにこのpathが入っているはずなので、基本的にはインストールだけで設定できるはず。

デフォルトだといくつか設定されており、それらを走査する実装になっているため、もし一意に絞れるようであれば絞るかソートで前に持ってきておくと良い。

 

$COMPLETIONS_BRACKETS

Trueの時、シェル入力時にPythonの括弧の補間を有効にする。実装自体はシンプルなので読めば大体分かると思う。

 

$FUZZY_PATH_COMPLETION

Trueであれば、pathをTab補完する時にfuzzyに補完する。あいまい補完。

> $FUZZY_PATH_COMPLETION = True
> ls desko 
./Desktop

具体的な実装としてはlevenshteinを使っており、以下を見れば良い。
https://github.com/xonsh/xonsh/blob/master/xonsh/tools.py#L854

 

$GLOB_SORTED

Trueであれば補完の結果をソートしてから表示する。

 

$SUBSEQUENCE_PATH_COMPLETION

Trueであれば、pathの補完において ~/w/t のような入力でも拡大解釈されて ~/work/tmp も候補に上がるようになる。
ディレクトリが長い時に雑に打って移動する時に使える。

 

$SUGGEST_COMMANDS

Trueであれば、無効なコマンドを入力した時に「もしかして?」を表示する。

f:id:vaaaaaanquish:20190825222513p:plain
$SUGGEST_COMMANDS=True

 

$SUGGEST_MAX_NUM

SUGGEST_COMMANDSがTrueの時に表示するコマンド数。負の値を入れておくと制限なしになる。

 

$SUGGEST_THRESHOLD

SUGGEST_COMMANDSやFUZZY_PATH_COMPLETIONでレーベンシュタイン距離を計算して、その閾値として扱う値。デフォルトは3。

 

$XONSH_AUTOPAIR

Trueであれば、括弧、括弧、引用符を自動挿入する。
keybindingsのfilterとして実装されているので、prompt toolkitにおけるfilterが何かを追ってから読むと大体実装もわかる。

 

$XONSH_HISTORY_MATCH_ANYWHERE

Trueであれば、上矢印で履歴を参照する時、現在の入力を検索語として接頭語以外でも補完する。
実装自体はprompt toolkitのbufferにおけるhistory matchなので、以下を参照すると良い。
https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/prompt_toolkit/buffer.py#L911

 

- ファイル、ディレクトリ操作に関するもの -

$AUTO_CD

Trueにしておくとcdコマンドを打たなくてもディレクトリ名だけで移動できる。

 

$CDPATH

bash, zsh等のCDPATHと同じ。cdコマンドが利用する相対パス。CDPATHによく使うディレクトリを入れておけば、cd hogeでショートカットのように移動できるので便利。一方で補間含めて全ての相対パスがこちらを参照するようになるので、使う場合は意図しないディレクトリに移動しないよう注意が必要。

 

$AUTO_PUSHD

DOSで言う所のディレクトリスタックに対して、cd時に自動でpushdする。pushd、popd、dirsコマンド*3を良く使うのであれば良い。

 

$DIRSTACK_SIZE

DOSで言う所のディレクトリスタックの最大保存数。実装自体は至極簡単なので読めば分かるはず。

  

$PUSHD_MINUS

ディレクトリスタックの操作pushd、popdに関するフラグ。Falseが通常のシェルコマンドのpushdで、Trueの場合はpushdとpopdが逆になる。

 

$PUSHD_SILENT

Trueならディレクトリスタックにpushdした時に表示する。デフォルトのFalseなら表示しない。

 

$DOTGLOB

Trueなら「*」や「**」を使ってglobした時にドットで始まるドットファイルを含むようにする。
実装はシンプルにreplaceで変換しているだけなので、読めば分かるはず。

 

- 表示に関するもの -

$PROMPT

xonshというよりはpython prompt toolkitの機能であり、ptk内のAppに対する引数に与える文字列に当たる。
https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/docs/pages/asking_for_input.rst#adding-a-bottom-toolbar

背景色や文字色も変更できるので、以下のドキュメントを参考にすると良い。
Asking for input (prompts) — prompt_toolkit 3.0.0 documentation
More about styling — prompt_toolkit 3.0.0 documentation

$PROMPTの表示。詳しく設定したい場合は、以下のチュートリアルを見ながら設定を進めると良い。
https://xon.sh/tutorial.html#customizing-the-prompt

 

$PROMPT_FIELDS

$PROMPTに対してオリジナルのフォーマットを関数等で指定できる。詳しく設定したい場合は、以下のチュートリアルを見ながら設定を進めると良い。
https://xon.sh/tutorial.html#customizing-the-prompt

 

$RIGHT_PROMPT

xonshというよりはpython prompt toolkitの機能であり、ptk内のAppに対する引数に与える文字列に当たる。
https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/docs/pages/asking_for_input.rst#adding-a-bottom-toolbar

背景色や文字色も変更できるので、以下のドキュメントを参考にすると良い。
Asking for input (prompts) — prompt_toolkit 3.0.0 documentation
More about styling — prompt_toolkit 3.0.0 documentation

右側のPROMPTに表示する文字列。詳しく設定したい場合は、以下のチュートリアルを見ながら設定を進めると良い。
https://xon.sh/tutorial.html#customizing-the-prompt

 

$BOTTOM_TOOLBAR

bottom toolbarを表示する文字列。空ならbottom toolbar無しになる。

f:id:vaaaaaanquish:20190824172521p:plain
bottom toolbar


xonshというよりはpython prompt toolkitの機能であり、ptk内のAppに対する引数に与える文字列に当たる。
https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/docs/pages/asking_for_input.rst#adding-a-bottom-toolbar

背景色や文字色も変更できるので、以下のドキュメントを参考にすると良い。
Asking for input (prompts) — prompt_toolkit 3.0.0 documentation
More about styling — prompt_toolkit 3.0.0 documentation

詳しく設定したい場合は、以下のチュートリアルを見ながら設定を進めると良い。
https://xon.sh/tutorial.html#customizing-the-prompt

 

$XONSH_GITSTATUS_*

xonshのgit statusのシンボル。gitコマンドがない場合はそれぞれsetされないので注意。

 

$XONSH_GITSTATUS_HASH

ハッシュ値。「git describe --always」と「git rev-parse --short HEAD」の値が違っていたら、XONSH_GITSTATUS_HASH+後者の値をHASHとして表示する実装。以下参照。
https://github.com/xonsh/xonsh/blob/master/xonsh/prompt/gitstatus.py#L89
デフォルト値は":"。

$XONSH_GITSTATUS_BRANCH

ブランチ名。デフォルト値は"{CYAN}"。

$XONSH_GITSTATUS_OPERATION

rebase中だとかmerge中だとかの状態シンボル。
[https://github.com/xonsh/xonsh/blob/master/xonsh/prompt/gitstatus.py#L104[
デフォルト値は"{CYAN}"。

$XONSH_GITSTATUS_STAGED

stageに乗っている時のシンボル。デフォルト値は"{RED}●"。

$XONSH_GITSTATUS_CONFLICTS:

コンフリクトが置きている時のシンボル。デフォルト値は"{RED}×"。

$XONSH_GITSTATUS_CHANGED

変更がある状態のシンボル。デフォルト値は"{BLUE}+"。

$XONSH_GITSTATUS_UNTRACKED

gitをトラックしていない時のシンボル。デフォルト値は"…"。

$XONSH_GITSTATUS_STASHED

stashした状態のシンボル。デフォルト値は"⚑"。

$XONSH_GITSTATUS_CLEAN

git上記の状態以外でclearな時のシンボル。デフォルト値は"{BOLD_GREEN}✓"。

$XONSH_GITSTATUS_AHEAD

masterブランチとのズレがaheadな状態を示すシンボル。デフォルト値は"↑·"。

$XONSH_GITSTATUS_BEHIND

masterブランチとのズレがbehindな状態を示すシンボル。デフォルト値は"↓·"。

 

$UPDATE_PROMPT_ON_KEYPRESS

Trueであれば、キー入力ごとにPROMPT表示を評価する。PROMPTに時計を実装したり、gitの状態を表示する時に有用。
実装もprompt toolkitにprompt周りの設定を関数で渡すか、stringで渡すかの違いでしかないのでシンプル。
実際評価しているのはptkなので、以下辺りから追っていくと良い。
https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/prompt_toolkit/shortcuts/prompt.py

 

$PROMPT_REFRESH_INTERVAL

PROMPTを更新する秒数を入れる。実装自体はprompt toolkitにあり、以下のサンプルのようにbottom_toolbarやrpromptでリアルタイムに更新される時計を作ったりできる。
https://github.com/prompt-toolkit/python-prompt-toolkit/blob/2.0/examples/prompts/clock-input.py

私が追加した変数なのだが、もちろん画面全体が指定秒でリフレッシュされて更新される訳なので、シェルの動作が重くなってしまう。(rpromptだけとか出来るようになっていくと良いな…)

 

$COLOR_INPUT

入力しているコマンドにsyntax highlightingをつける。実装自体は、prompt toolkit及びpygmentsによる実装なので以下を参考にすると良い。
https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/prompt_toolkit/lexers/pygments.py#L145

 

$COLOR_RESULTS

出力結果にsyntax highlightingをつける。実装自体は、上記のCOLOR_INPUTと同じくprompt toolkit及びpygmentsによる実装なので以下を参考にすると良い。
https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/prompt_toolkit/lexers/pygments.py#L145

 

$PRETTY_PRINT_RESULTS

Tanaka Akira氏が作っているrubyのprettyprint.rbを使って、出力を表示するかどうかのフラグ。
以下のドキュメントの通り、単体でもapiとして使えるようにはなっている。
https://xon.sh/api/pretty.html

 

$DYNAMIC_CWD_WIDTH

$PROMPTに表示するディレクトリを省略する時の文字数閾値
(float, str)のtupleで、0番目に閾値を入れる。float('inf')を入れる事もできる。
1番目にはstringの"%"かそれ以外が入る。"%"を入れた場合はShellの画面サイズに対する0番目の値のパーセンテージになる。"%"以外を入れたらシンプルに0番目がintになって文字数で閾値になる。

 

$DYNAMIC_CWD_ELISION_CHAR

$PROMPTに表示するディレクトリ名が長い場合に省略形として出す文字列。省略する時の文字数の閾値はDYNAMIC_CWD_WIDTHに依存。

 

$INDENT

複数行入力のインデント文字列

f:id:vaaaaaanquish:20190825181526p:plain
$INDENT="...."
実装は、改行時やkeybindで指定の文字列を挿入しているだけ。

 

$LS_COLORS

bash, zsh同様、lsコマンドの出力の色指定。基本的には他のシェルのものが参考になるし、実装を見て色名でのstring指定もできる。

 

$XONSH_COLOR_STYLE

xonshのcolorを統一的に変更できるthemeのような機能。適応できるstyleは「xonfig styles」で確認できる。
最近はPTK_STYLE_OVERRIDESやPROMPT_TOOLKIT_COLOR_DEPTHで柔軟に変更できるようになったが、このtheme機能も簡単に良い感じにできるので良さはある。

 

$PTK_STYLE_OVERRIDES

補完の表示色や、背景色、プロンプトの色などあらゆる色を設定するところ。

f:id:vaaaaaanquish:20190825200508p:plain
$PTK_STYLE_OVERRIDES
設定できる項目とデフォルト値の対応は以下の通り

  • "completion-menu": "bg:ansigray ansiblack"
  • "completion-menu.completion": ""
  • "completion-menu.completion.current": "bg:ansibrightblack ansiwhite"
  • "scrollbar.background": "bg:ansibrightblack"
  • "scrollbar.arrow": "bg:ansiblack ansiwhite bold"
  • "scrollbar.button": "bg:ansiblack"
  • "auto-suggestion": "ansibrightblack"
  • "aborting": "ansibrightblack"

デフォルト値参考:https://github.com/laloch/xonsh/blob/master/xonsh/style_tools.py#L347

 

$PROMPT_TOOLKIT_COLOR_DEPTH

色の深度を決定できる変数。ドキュメントには書いていないが、コード上では以下の値が指定できる。

  • "DEPTH_1_BIT"
  • "MONOCHROME"
  • "DEPTH_4_BIT"
  • "ANSI_COLORS_ONLY"
  • "DEPTH_8_BIT"
  • "DEFAULT"
  • "DEPTH_24_BIT"
  • "TRUE_COLOR"

 

$MULTILINE_PROMPT

コマンドを改行した際に表示される幅寄せ用の文字。

f:id:vaaaaaanquish:20190825184844p:plain
$MULTILINE_PROMPT="@"

 

$TITLE

ターミナルのタイトルを設定する。実装自体は以下のように非常にシンプルで、これで設定できないターミナルエミュレータを使っている場合は無意味。

if ON_WINDOWS and "ANSICON" not in env:
    kernel32.SetConsoleTitleW(t)
else:
    with open(1, "wb", closefd=False) as f:
        f.write("\x1b]0;{0}\x07".format(t).encode())
        f.flush()

iTerm2でタブ名が変わる例

f:id:vaaaaaanquish:20190825234117p:plain
$TITLE="Hoge"

 

$XONSH_SHOW_TRACEBACK

Trueであれば、xonshコマンドがエラーの時にtracebackを全て表示する。長くなるのでデフォルトはFalse。

 

$XONSH_STDERR_*

stderrの出力に接頭語、接尾語を付ける。stderrの色を設定したい時などに使える。
stdio周りはxonsh独自にTeeクラスが実装されていて、そこを通るのでそちらも参考に。

$XONSH_STDERR_POSTFIX

stderrの出力に付く接頭語。

$XONSH_STDERR_PREFIX

stderrの出力に付く接尾語。

 

- コマンドやxonshスクリプトの動作、変数の扱いに関するもの -

$EXPAND_ENV_VARS

Trueであれば「$var」「${var}」「%var%」をサブプロセスモードで自動で展開する。
実装自体はbuiltins.__xonsh__.envの中にあれば変換するし、なければそのまま文字列で利用するとなっている。

> $EXPAND_ENV_VARS=True
> echo "my home is $HOME"
my home is /Users/shunsuke.kawai

> $EXPAND_ENV_VARS=False
> echo "my home is $HOME"
my home is $HOME

あまりFalseにする必要はないと思うが、format string等で不具合が出た時にはこちらの設定を確認すると良い。

 

$FOREIGN_ALIASES_OVERRIDE

zsh上でxonshコマンドでxonshを起動する等、外部シェルから呼び出されている場合にxonshrc等で設定するaliasを外部シェルのaliasに対してOverrideするかどうか。
扱いが難しく xonshが起動した時点でaliasesの読み込みは行われてしまうので、xonshrcやxonsh上のコマンドとして設定しても動作しない。
呼び出し元のbashzsh環境変数としてFOREIGN_ALIASES_OVERRIDEを設定しておく必要があるので注意。

 

$FOREIGN_ALIASES_SUPPRESS_SKIP_MESSAGE

外部シェルのaliasが既に存在する時、xonsh上でOverrideしようとすると警告を出すaliasコマンドが一部存在する。その時のメッセージを表示しないようにするためのフラグ。
基本的にはTrueにしておいて問題ないと思う。

 

$HISTCONTROL

履歴に保存するコマンドを選択する。
setの中にstringで"ignoreerr"を入れておくと、終了ステータスが0以外の正常終了しなかったコマンドを履歴に追加しない設定となる。
setの中にstringで"ignoreerr"を入れておくと、同じコマンドを連続で入力した時に保存しない設定となる。

 

$LANG

historyファイル等で利用する文字コード。あくまでxonsh内での文字コードなので、デフォルトのutf-8以外にする必要に迫られる場合は殆どないはず。

 

$SHELL_TYPE

シェルのコアライブラリを選択する。以下の4つのstringが指定できる。

  • "readline"
  • "prompt_toolkit"
  • "random": ランダムにptkかreadlineを起動する
  • "best": 環境に応じてptkかreadlineを起動する

基本的にというか"prompt_toolkit"でないとxonshの恩恵の殆どが受けられないので一択だと思う…readlineだとほぼbash…randomとかどういう時に使うんだろう…

 

$RAISE_SUBPROC_ERROR

Trueであれば、サブプロセス経由のコマンドが異常終了した場合にsubprocess.CalledProcessErrorを発生させる。デフォルトはFalse。
何も表示せず、ステータスコードを返さず終了するようなモジュールを使う時に役立つかも…?

 

$XONSH_APPEND_NEWLINE

Trueであれば、コマンドの結果の末尾に改行を追加する。
普通のシェルの動作として使う場合はTrueで、「xonsh -c hoge.py」のようにしてxonshを動かす場合に余計な改行が入ってしまうのでFalseにするためのもの。
デフォルト値にはXONSH_INTERACTIVEの値が入っており、基本的にインタラクティブシェルだったらTrueになるようになっている。

 

- キーバインドや操作に関するもの -

$IGNOREEOF

Trueであれば、「ctrl + d」でシェルを終了しない。
コマンド入力を待機し続けるループをbreakするかしないかという実装。
https://github.com/xonsh/xonsh/blob/master/xonsh/ptk2/shell.py#L186

 

$MOUSE_SUPPORT

Trueであれば、シェル上でマウス操作を許可する。
実装自体はprompt toolkitの機能で、ptkの方を見に行くと良い。以下のexampleを実行すれば分かるように、マウスクリックでカーソルを移動させたりできる。
https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/examples/prompts/mouse-support.py
エディタやセレクターっぽい機能を実装する時にも使えるかも。

 

- フラグや変数として利用するもの -

$LOADED_RC_FILES

xonshrcが読み込まれたかどうかのフラグをlistにしたもの。n番目が何のrcファイルなんだ…

 

$OLDPWD

一つ前に居たディレクトリの文字列が入る。起動時はcdするまでEnvがセットすらされてないので注意が必要。

 

$PATH

各シェルで言う所のいわゆるPATH。binとかスクリプトは個々に入っているディレクトリを見に行って実行する。
listなのでappendとかで追加する。bulitins.__xonsh__.env["PATH"]からもよく参照される。

 

$TERM

基本的にはユーザが設定する事はなく、ターミナルエミュレータが与える変数。以下のようにWindowsエミュレータの判定とかに使われる。
https://github.com/xonsh/xonsh/blob/master/xonsh/base_shell.py#L500

例えば以下のissueのように、Windows+AnacondaをWSL上から触っていてターミナルエミュレータが認識できない場合等に強制的に指定する場合に使う。
Unknown Terminal type using conda env (Windows 10 --> WSL) · Issue #2525 · xonsh/xonsh · GitHub
指定する時はドキュメントの通り、早い段階(xonsh起動時の引数、xonshrcの上部)で設定しないと他設定が読み込まれる。

 

$XONSH_INTERACTIVE

xonshがインタラクティブシェルとして起動しているか、「xonsh -c hoge.py」のようなスクリプト実行形式で起動しているかのフラグ。

 

$XONSH_LOGIN

xonshがログインシェルとして起動しているかのフラグ。

 

$XONSH_SOURCE

xonshスクリプト実行時、スクリプトへの絶対pathが入る変数。スクリプト実行時意外はセットされていない。

- おわりに -

大体これで1.0.0までカバーできるはず。

今回「XDG_CONFIG_HOME」「XONSH_CONFIG_DIR」「XONSH_HISTORY_FILE」など非推奨やもう機能していないものを抜いているので注意。PRを出して改修するのが良さそう。

 

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アプリケーション等が出てくる事を期待している。


 

xonsh拡張のxontribをつくる

- xontribとは -

xontribはxonshの拡張ライブラリ群である。
公開されているxontribを導入するには以下のようにライブラリをインストールしロードする。

pip install xontrib-hoge
xontrib load hoge

xontrib loadをxonshrcに記述しておくことで、xonshセッション起動時にロードすることもできる。

1年程前に公開されていた各xontribの紹介は以下記事に記載している。
vaaaaaanquish.hatenablog.com

 
xontribはpythonスクリプトもしくはxshスクリプトによって記載されたmoduleで、xontrib以下のnamespaceに配置される。tabcomplete拡張や外部Pythonライブラリのサポート、xonshモジュールのoverride等が作成、公開されており、その作成方法については以下チュートリアルに概要が書かれている。
xon.sh
本記事はこちらを参考にし、xontribを作成していくものである。


 

- cookiecutterによるtemplete利用 -

xontribのテンプレートとしてcookiecutterを利用する事ができる。

https://github.com/laerus/cookiecutter-xontribgithub.com


以下コマンドで対話的にライブラリの雛形が作成できる。

pip install cookiecutter
cookiecutter https://github.com/xonsh/xontrib-cookiecutter

対話の内容は以下のようになる

full_name [Your name]: vaaaaanquish
email [Your address email]: sample@exm.com
github_username [Your github username]: 6syun9
project_name [Name of the project]: xontrib-sample    # setup.pyに記載されるproject
project_slug [xontrib-samplelib]: sample              # xontrib loadするモジュール名
project_short_description [A short description of the project]: This is test
version [0.1.0]: 0.0.1

上記cookiecutterコマンドによってsampleディレクトリが作成される。

$ tree sample
.
├── LICENSE
├── README.rst
├── setup.py
└── xontrib
    └── sample.xsh

作成した後は、sample.xshを編集するだけである。
作成された時点ではsample.xshには内容がないため、以下のように変更する。

import xonsh

__all__ = ()
__version__ = '0.0.1'

print('hello xontrib sample')

xontribを読み込んだ時点でstringをoutputするサンプルとなる。
こちらを手元にinstallし、xontrib loadで読み込んでみる。
sampleディレクトリ内にあるsetup.pyを利用する。

$ python setup.py install
$ xontrib load sample
hello xontrib sample

ここから中身を拡充していくだけ。

 

- pypiで公開し使ってもらう -

pip installで導入できるようにpypiに登録する。
(アカウント作成が必要なので事前に行なう)
PyPI · The Python Package Index

twinを利用したアップロード

distを作成し公開するためのツールを導入する

pip install wheel twine

先程の作成したパッケージのroot dirのsetup.pyを利用してパッケージを作成、アップロードする。

python setup.py sdist bdist_wheel
twine upload --repository-url https://upload.pypi.org/legacy/ dist/*

数分後くらいにちゃんと登録されて pip install xontrib-sample できるようになる(この記事は解説なので実際にはやってない)。

pypiのREADMEや必要な外部ライブラリを定義する

最低限以下のようにしておくとなんとかなる。
https://github.com/6syun9/xontrib-readable-traceback/blob/master/setup.py

rstじゃなくてmdじゃないとgithubレンダリングしてくれない場合があるので、私はmdで管理している。

 

xonshのDocumentationに追加してもらう

公開されているxontribはxonshのドキュメント上に載せてもらう事ができる。
xon.sh

また xontrib list コマンドでも表示されるようになる。

こちらに載せてもらうには、本家xonshにpull requestsを送る必要がある。
例えば、以下のように「新しいxontrib作ったよ!」と xonsh/xontribs.json を編集した旨を伝えるpull requestsを作成する。
github.com

xonshにコントリビュートする時は news/TEMPLETE.rst をコピーして news/{{ブランチ名}}.rst なるファイルを作って、更新ログを書くルールになっているのでそちらも追加して終了。詳しくは以下Developer’s Guideを参照。

Developer’s Guide — xonsh 0.9.24 documentation


 

- xontrib作成において参考にできる所 -

「こういうの作ってみたいなあ…」と思った時に参考にできるよう私のメモとして記載しておく。

あるタイミングでコマンドを発火させたい

tab補完を拡張したい

promptの見た目を弄りたい

コマンドの保存、読み込み、引数実装

コマンドの出力結果を使いたい

xonsh自体の機能をoverrideしたい

別threadの実装

画像やgraphを表示したい

サブウインドウやalertを表示したい

ネタがない


 

- おわりに -

このメモとGoogleが使えればxontribが作れると思うので、みなさんもxontributerになりましょう。