Stimulator

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

xonshのCore Eventsまとめ

- xonshのCore Eventsとは -

XonshのCore Eventsは、xonshを自分で改修していく上で大事な「xonshの動作をtriggerとして発火するもの」です。

xonshの良いところは、EventsをPythonの関数のデコレータとして記述する事だけで「EventをHookして何かを実行する」という事が可能になる所です。


公式ドキュメントのsampleを例に見てみます。

# cdなど移動系コマンドのイベントをトリガーにadd_to_fileが起動する
@events.on_chdir
def add_to_file(olddir, newdir, **kw):
    with open(g`~/.dirhist`[0], 'a') as dh:
        print(newdir, file=dh)

上記コードでは、ディレクトリ移動が発生したタイミングで移動情報をファイルに追記するようになっています。
参考 : Tutorial: Events — xonsh 0.6.0.dev151 documentation


Core Eventsを把握しておくことは、Xonshの理解に繋がる訳です。


 

- Core Events -

以降はCore Eventsについて記述しておくものです。

主に以下に記載されたxonshに内装されているEventsについてです。
Core Events — xonsh 0.6.0.dev151 documentation

 

設定周り

rcファイル読み込み前

on_pre_rc() -> None
http://xon.sh/events.html#on-pre-rc-none
設定ファイルとなるxonshrcを読み込む前に発火するイベント
正確には以下の中で発火するので、起動時で言うとon_post_initの直前
https://github.com/xonsh/xonsh/blob/88647b4a8f6f2611dbb03370879221b1ca9fa89a/xonsh/main.py#L251

 

rcファイル読み込み後

on_post_rc() -> None
http://xon.sh/events.html#on-post-rc-none
設定ファイルとなるxonshrcを読み込んだ後に発火するイベント
正確には以下の中で発火するので、起動時で言うとon_post_initの直前
https://github.com/xonsh/xonsh/blob/88647b4a8f6f2611dbb03370879221b1ca9fa89a/xonsh/main.py#L251

 

初期化後

on_post_init() -> None
http://xon.sh/events.html#on-post-init-none
Xonshスクリプトや対話コンソール等のための初期化が済んだ後に発火するイベント
対話コンソールやスクリプト等関係なく発火する
この後対話コンソールとして起動した場合は、on_pre_cmdloopが発火する

 

cmdloopに入る時

on_pre_cmdloop() -> None
http://xon.sh/events.html#on-pre-cmdloop-none
xonshrcや各moduleのimportを終えて、対話コンソールが入力待ち状態に入る前に発火するイベント
正確な場所は以下
https://github.com/xonsh/xonsh/blob/88647b4a8f6f2611dbb03370879221b1ca9fa89a/xonsh/main.py#L347

 

cmdloopから抜けた時

on_post_cmdloop() -> None
http://xon.sh/events.html#on-post-cmdloop-none
xonshがコマンド入力やPythonスクリプトを受け続ける状態を抜けた時に発火するイベント

 

ptkを読み込んだ時

on_ptk_create(prompter: Prompter, history: PromptToolkitHistory, completer: PromptToolkitCompleter, bindings: KeyBindingManager)
http://xon.sh/events.html#on-ptk-create-prompter-prompter-history-prompttoolkithistory-completer-prompttoolkitcompleter-bindings-keybindingmanager
xonshが利用するptkを読み込んだ時に、ptkを渡してくれる
ptkを使ったキーバインドの設定等でよく使う
正確には以下がinitされた時に発火するイベント
https://github.com/xonsh/xonsh/blob/72f3bc0d089ea91d4e5288bb1c44ebfbe81db43e/xonsh/ptk/shell.py#L34

 

exitする前

 
on_exit() -> None
http://xon.sh/events.html#on-exit-none
正確にはmain_xonsh内のfinally内でこのイベント発火だけ発生して、その後はxonshが落ちて別のlogin shellが起動される
https://github.com/xonsh/xonsh/blob/88647b4a8f6f2611dbb03370879221b1ca9fa89a/xonsh/main.py#L347
このイベントを起点にExceptionを落とすとlogin shell出ない
上記on_post_cmdloopの直後に発火する形だが、違いは対話コンソールの時以外でもon_exitは発火する点である
スクリプトなどでもhookしたい場合に使う


 

moduleをインポートする時

インポートする前

on_import_pre_create_module(spec: ModuleSpec) -> None
http://xon.sh/events.html#on-import-pre-create-module-spec-modulespec-none
import hogeする前に発火するイベント
指定した”hoge”がspecとして渡される
 
on_import_pre_exec_module
http://xon.sh/events.html#on-import-pre-exec-module-module-module-none
loader.create_moduleではなくloader.exec_moduleによってimportされる前に発火するイベント
Pythonでは主にexec_moduleが使われていくようになる(Python 3.4でexec_moduleが追加され、3.6以降はメインで使われるように)
動作は上と同じ

インポートする後

 
on_import_post_create_module(module: Module, spec: ModuleSpec) -> None
http://xon.sh/events.html#on-import-post-create-module-module-module-spec-modulespec-none
import hogeした後に発火するイベント
create_moduleしたhogeの内容がmodule、hogeがspecとして渡される
 
on_import_post_exec_module
http://xon.sh/events.html#on-import-post-exec-module
loader.create_moduleではなくloader.exec_moduleによってimportされた後に発火するイベント

 

moduleを探す前

on_import_pre_find_spec(fullname: str, path: str, target: module or None) -> None
http://xon.sh/events.html#on-import-pre-find-spec-fullname-str-path-str-target-module-or-none-none
moduleを探すためのimportlib.util.find_specが利用される前に発火するイベント
以下find_specの引数に指定した名前やPathが渡される
https://docs.python.org/3.6/library/importlib.html#importlib.util.find_spec

 

moduleを探した後

on_import_post_find_spec(spec, fullname, path, target) -> None
http://xon.sh/events.html#on-import-post-find-spec-spec-fullname-path-target-none
moduleを探すためのimportlib.util.find_specが利用された後に発火するイベント
module本体や名前、Pathなどが渡される

 

環境変数を触った時

新規環境変数作成時

on_envvar_new(name: str, value: Any) -> None
http://xon.sh/events.html#on-envvar-new-name-str-value-any-none
環境変数作成時に発火するイベント
新しい変数名と値が渡される
on_envvar_new内で環境変数を変更すると、変数アプデ -> on_envvar_new -> 変数アプデ -> on_envvar_new -> … とループに陥るので注意
 
 

環境変数アップデート時

on_envvar_change(name: str, oldvalue: Any, newvalue: Any) -> None
http://xon.sh/events.html#on-envvar-change-name-str-oldvalue-any-newvalue-any-none
環境変数アップデート時に発火するイベント
変数名と前の値と新しい値が渡される
on_envvar_newと同じくループに注意

 

コマンド実行時

ディレクトリ移動時

on_chdir(olddir: str, newdir: str) -> None
http://xon.sh/events.html#on-chdir-olddir-str-newdir-str-none
cdコマンド等でディレクトリが移動した際に発火するイベント
ディレクトリの移動先と移動元が渡される

   

コマンド実行(コマンド変換時)

on_transform_command(cmd: str) -> str
http://xon.sh/events.html#on-transform-command-cmd-str-str
コマンドを実行する前に発火するイベント
以下で発火するため、複数行の場合繰り返し実行されたりする
https://github.com/xonsh/xonsh/blob/f05c4d86b8529703832c2f0bb5fe2790dd3c5b66/xonsh/shell.py#L60
正確には以下のpushメソッドで「compileされる時」であり、実行時ではない。
https://github.com/xonsh/xonsh/blob/72f3bc0d089ea91d4e5288bb1c44ebfbe81db43e/xonsh/ptk/shell.py#L123

 

コマンド実行前

on_precommand(cmd: str) -> None
http://xon.sh/events.html#on-precommand-cmd-str-none
コマンド実行前に発火するイベント
入力された文字列が渡される

 

コマンド実行後

on_postcommand(cmd: str, rtn: int, out: str or None, ts: list) -> None
http://xon.sh/events.html#on-postcommand-cmd-str-rtn-int-out-str-or-none-ts-list-none
コマンド実行後に発火するイベント
コマンド名やioが渡される

 

一行処理前

on_pre_prompt() -> None
http://xon.sh/events.html#on-pre-prompt
(ドキュメントにはon_first_promptとなっているがミスか)
対話コンソールで一行を読み込んで処理する前に発火するイベント
正確な場所は以下で、入力をhistoryに追加した後である
https://github.com/xonsh/xonsh/blob/5cb3e8dd5545a7448b2a393379e3405c1942d1e0/xonsh/readline_shell.py#L276

 

一行処理後

on_post_prompt() -> None
http://xon.sh/events.html#on-post-prompt
対話コンソールで一行を読み込んで処理した後に発火するイベント


  

- おわりに -

Core Eventsは大事なEventで実装されているイベント以外にも、自前でEventを継承してXonsh内にOverrideさせる事もできるので、xonshの動作をかなり拡張することが可能である。

また、逐一追っていくとXonshの動作のデバッグにもなるので、一度触ってみると吉。

 
アドベントカレンダーまだ空いてるから頼むという所で記事はおしまいです。

qiita.com

 

XonshのException発生時のtracebackを見やすくする

- はじめに -

この記事は、Xonsh Advent Calendar 2017 - Qiita 15日目の記事です。

完全に遅刻しています。

3日間xonsh本家のコードを読みながら「あーでもないこーでもない」とやっており遅れました。

結論を先に述べてから、後半でその経緯も話します。

 

Xontribにしておいたので簡単に使えると思います。



アジェンダ

 

- PythonのStackTraceを見やすくする -

PythonのStackTraceを見やすくする方法を以下の記事に書きました。

vaaaaaanquish.hatenablog.com

この中でも、私が最も見やすいと感じたbacktraceを使って、xonshのTraceBack表示を短くします。

他のパッケージや、自作のシンタックスハイライト用parserでも上手くいくと思います。


 

- xonshのstderrにbacktraceを適応する -

結論から言うと以下のコードを~/.xonshrcに差し込むのが早いと思います。

import xonsh.tools
import backtrace
import sys

# backtraceパッケージの_flush()をOverride
# 元コード : https://github.com/nir0s/backtrace/blob/f2c8683ec53e4fa48ea8c99c196b201bf22fda3e/backtrace.py#L36
def __flush(message):
    st = message + '\n'
    sys.stderr.buffer.write(st.encode(encoding="utf-8"))
backtrace._flush=__flush

# xonshのprint_exception()をOverride
# 元コード : https://github.com/xonsh/xonsh/blob/230f77b2bc64cbc3e04837377252793f5d09b9ba/xonsh/tools.py#L798
def _print_exception(msg=None):    
    tpe, v, tb = sys.exc_info()
    backtrace.hook(tb=tb, tpe=tpe, value=v)
    if msg:
        msg = msg if msg.endswith('\n') else msg + '\n'
        sys.stderr.write(msg)
xonsh.tools.print_exception = _print_exception

※上記xonshrcは最小限のコードです


こんな感じになり、コンソール汚染が減ってハッピーです。
一部ユーザ名を加工しています。
f:id:vaaaaaanquish:20171218231935p:plain

特に後者のpandasのkey errorなどは、本来30行近いエラーが出力されるのですが、半分程度に収まっていますし、かなり作業がしやすいかと思います。

 
backtrace._flush()は元のソースコードが以下のようになっています。

def _flush(message):
    sys.stderr.write(message + '\n')
    sys.stderr.flush()

後述しますが、xonshはsys.stderr.bufferが使える環境であればsys.stderr.buffer.write、使えなければsys.stderr.writeを利用してエラー内容を出力するようになっています(ここの調査に1日半かかりました)。
よって、backtraceの_flushをOverrideする時は、適切にPython環境に合わせてどちらかを選択して書き換えてやるのが正解です。

上記のxonshrcより丁寧に、sys.version_infoでバージョンチェックをして書いてやると良いです。
私はPython2以下は小学生に馬鹿にされるので使わないため利用してません。

 
また、xonshではエラー発生時に「tracebackを取得して、ログに保存して、メッセージを表示する」といった内容をまとめたxonsh.tools.print_exceptionを必ず呼ぶようになっています。
上記xonshrcコードでは、そのxonsh.tools.print_exceptionの」ほぼ全てをそぎ取ってbacktraceに任せるという形を取っていますが元のソースコードはそこそこ長くちゃんとしています。

元の機能をなるべく壊さないようOverrideする場合は、元ソースでtraceback.print_exc()となっている所をsys.exc_infoからのbacktrace.hockに書き換えてやるのが一番良いと思います。


 

- さらに見やすくするためにcoloramaのStyleを記述する -

backtraceパッケージでは、coloramaというANSIカラーコードを利用したカラーリングパッケージを使って、さらにStyleを変更する事が可能です。

上記xonshrcのbacktrace.hookする部分で、reverse(逆順表示)やstrip_path(ファイル名のみ表示)、styleのパラメータを設定してやれば良いです。
私は以下のようにしています。

import backtrace
from colorama import init, Fore, Style
STYLES = {
    'backtrace': Fore.YELLOW + '{0}',
    'error': Fore.RED + Style.BRIGHT + '{0}',
    'line': Fore.RED + Style.BRIGHT + '{0}',
    'module': '{0}',
    'context': Style.BRIGHT + Fore.GREEN + '{0}',
    'call': Fore.RED + '--> ' + Fore.YELLOW + Style.BRIGHT + '{0}',
}
backtrace.hook(reverse=True, strip_path=True, styles=STYLES, tb=tb, tpe=tpe, value=v)

これで以下のようにさらに情報量が減り、順番もトップを見るだけという感じになりました。
f:id:vaaaaaanquish:20171218230912p:plain

やったね。


 

- どのようにprint_exceptionが呼ばれているか -

ここからは蛇足です。
数日詰まっていた理由と解決に至った流れを書いておくものです。

 
以下のtools.print_exception()があらゆるException発生時に呼ばれる事はDocを見ても明確である。
xonsh/tools.py at 230f77b2bc64cbc3e04837377252793f5d09b9ba · xonsh/xonsh · GitHub
Tools (xonsh.tools) — xonsh 0.6.0.dev151 documentation

そして、xonshのshell上で入力される全てのコマンドやPythonスクリプトは、base_shell.default()にてコンパイルされ実行される。
https://github.com/xonsh/xonsh/blob/ff05ec33a22c1688674616a84ef66d65cef5b3c5/xonsh/base_shell.py#L313

default内3行目のpushメソッドが入力されたコードをcompileするものである。
ptk構成のshellの場合、以下_pushでOverrideされている。
https://github.com/xonsh/xonsh/blob/ff05ec33a22c1688674616a84ef66d65cef5b3c5/xonsh/ptk/shell.py#L123

正確にpushがOverrideされている箇所を示すと以下cmdloop。
https://github.com/xonsh/xonsh/blob/ff05ec33a22c1688674616a84ef66d65cef5b3c5/xonsh/ptk/shell.py#L145

 
pushは降ってきたスクリプトPythonのexecer.compileに投げてコンパイルして返すものである。

push時に@(x=1/0)とすればSyntaxErrorをコンパイラが検知してExceptionを返す。
しかし、@(1/0)は算術なので実行時にExceptionが出る(ここでかなり躓いた)。

 
実行時のExceptionというのは、ここ(run_compiled_code)で走った結果起こったものである
https://github.com/xonsh/xonsh/blob/ff05ec33a22c1688674616a84ef66d65cef5b3c5/xonsh/base_shell.py#L330

xonsh.codecache.run_compiled_codeはただのexecするだけの関数である
https://github.com/xonsh/xonsh/blob/f544e63699a19d3990d2abf1ff082c9b5f48176d/xonsh/codecache.py#L58


pushもrun_compiled_codeもException発生時、tools.print_exceptionが呼ばれており、一見違いが分かりにくい。

ここで出てくるのが独自のstreem io用のClassであるTee。
このTeeは、Pythonのバージョンの違い(sys.hoge.bufferのあるなし)を考慮しつつ、stdoutとstderrを同じIOで処理しながら、エラー前に$XONSH_STDERR_PREFIX、後に$XONSH_STDERR_POSTFIXを付けて、ログに保存しながら出力するためのものである。
https://github.com/xonsh/xonsh/blob/ff05ec33a22c1688674616a84ef66d65cef5b3c5/xonsh/base_shell.py#L217

以下で_TeeStdインスタンス作った時sys.stderrやsys.stdoutを関連付けしてる。
https://github.com/xonsh/xonsh/blob/ff05ec33a22c1688674616a84ef66d65cef5b3c5/xonsh/base_shell.py#L238
https://github.com/xonsh/xonsh/blob/ff05ec33a22c1688674616a84ef66d65cef5b3c5/xonsh/base_shell.py#L132


この_TeeStdがsys.stderr.buffer.write(byte入力)を利用しているため、backtraceが利用するsys.stderr.write(string入力)とで噛み合わない上、Teeのインスタンス生成タイミングはコードをcompileした後なので「@(x=1/0)の時はcoloramaのcolorが適応されるけど、@(1/0)の時は適応されない何で…」という事象が発生し、xonshの動作を逐一追うことになった。

これが全貌。


 

- おわりに -

最終的にbacktrace._flushもsys.stderr.buffer.writeに合わせてやる事で解決したけど、絶対もっとスマートに出来ると思う。
というかTeeのインスタンス作るタイミングは、普通にissue立てても良さそう。


Xonshアドベントカレンダーも空きの日のネタを用意してたのに書けてないしなかなか…という感じ。

誰か書いて下さい…

qiita.com

 

PythonのException発生時のTracebackを綺麗に見る

- はじめに -

PythonOSSパッケージ等を利用していると、Exceptionが発生した際に表示されるTraceback(正確にはスタックトレース)がかなり長い場合がある。

例えば、以下の簡易なコード実行で表示されるTracebackの行数は30近くなる。

import pandas as pd
df = pd.DataFrame(dict(a=[1,2,3]))
df['b']

引用 : python - Shorten large stack traces when using libraries - Stack Overflow


より複雑なプログラムにおいては、この比ではない。
にも関わらず、記述ミスのようにTraceback上位部にエラーの重要な内容がある場合もあれば、パッケージ内部のValidationで下位部が重要な場合もある。

得てしてPython開発環境として利用されるxonsh等の対話コンソール上やJupyter notebook等で100行近いエラーが出てきた時は、少なからず気持ちが折れる。

これらは、様々な言語の資産の利用などから引き起こされる長さである。
また設計上、過度なWrappingが行われた結果長くなっている場合も多々ある。

本記事は、主にPython3系において、Exceptionをcatchした時のTracebackを出来る限り綺麗に表示させ、あくまで開発のために人に優しい環境にするためのTipsやパッケージをまとめるものである。


※ 長く書いたが要約すると「xonshで作業してる時にクソ長いエラーが出てきてコンソール汚染されて腹立つから何とかしたい」と思っていたが調べてるうちに知見が溜まったのでまとめるという話


結論から言うとbacktraceかTBVaccineが良さそう。
 
もくじ:

 

- Tracebackの表示 -

そもそもPythonにおけるStackTraceは、Exception発生時にsys.last_tracebackに変数にtracebackオブジェクトとして格納される。

その中身をよしなに参照するためにsys.exc_info()が用意されている。
sys.exc_info()は返り値が(value, type, traceback)となっている長さ3のtupleなので、それらをtraceback.format_hogeに投げて以下のように直接中身を参照する事ができる。
(もちろんこんな実装を実際にしている人は居ないと思うが)

import sys
import traceback

try:
    x = 1 / 0    # ゼロ除算
except Exception as e:
    t, v, tb = sys.exc_info()
    print(traceback.format_exception(t,v,tb))
    print(traceback.format_tb(e.__traceback__))

# >>> ['Traceback (most recent call last):\n', '  File "<stdin>", line 2, in <module>\n', 'ZeroDivisionError: division by zero\n']
# >>> ['  File "<stdin>", line 2, in <module>\n']


tracebackモジュールには、traceback.format_exctraceback.print_excが用意されているので実際はこちらで十分である。

import traceback

try:
    x = 1 / 0    # ゼロ除算
except:
    print(traceback.format_exc())    # いつものTracebackが表示される
    traceback.print_exc()                 # これでも同じ

 
参考 : 29.9. traceback — Print or retrieve a stack traceback — Python 3.6.4 documentation


 

tracebacklimitの利用

Pythonでは、sys.tracebacklimitでトレースバック情報のレベル値を設定できる(0〜1000)。

そして0から1000と書いたが、この機能は既知のバグとしてPython 3.xで機能しない。
Issue 12276: 3.x ignores sys.tracebacklimit=0 - Python tracker

一応Noneにすることで、値とエラー内容だけ出せる。

import sys

sys.tracebacklimit=None
x = 1 / 0

# >>> ZeroDivisionError: division by zero

これなら str(Exception) で十分。
2系を使っていないならあまり恩恵が得られない。

また、traceback.format_exc(limit=1)等とした場合も同様に見える。


参考 : 29.1. sys — System-specific parameters and functions — Python 3.6.4 documentation

 

ColoredTracebackでシンタックスハイライト

Tracebackの表示にカラーリングするパッケージがある。

github.com

導入はpip

pip install colored-traceback
pip install colorama    # Windows環境下の場合

基本的には import colored_traceback.always としておけば良い

f:id:vaaaaaanquish:20171214053335p:plain


基本的にSyntaxはIPythonならサポートしてくれてるので、主にコンソールで作業する時用に。

 

Pygmentsでシンタックスハイライト

上記と同じ事がPygmentsでもできる(こちらの方が一般的か)。
Available lexers — Pygments

以下のようにsys.excepthookをOverrideするための関数を作ってやればよい。

import sys
import traceback
from pygments import highlight
from pygments.lexers import get_lexer_by_name
from pygments.formatters import TerminalFormatter

def myexcepthook(type, value, tb):
    tbtext = ''.join(traceback.format_exception(type, value, tb))
    lexer = get_lexer_by_name("pytb", stripall=True)
    formatter = TerminalFormatter()
    sys.stderr.write(highlight(tbtext, lexer, formatter))

sys.excepthook = myexcepthook

エラーがカラーリングされて見やすくなる。


   

- パッケージの利用 -

StackTrace表示を見やすくするための関連パッケージを示す。
backtraceが短く表示するやつで、それ以降は基本的に詳細表示のパッケージにあたる。

 

backtrace

StackTraceを短くしてくれるパッケージ。
上記colored-tracebackを使ってカラーリングもされる。

github.com

導入はpipで。Winの場合は上記colored-tracebackのcoloramaを先にinstallしておくと吉。

pip install backtrace


「はじめに」に記載のコードを実行してみる。

import pandas as pd
import backtrace

backtrace.hook(
    reverse=True,         # 逆順
    strip_path=True    # ファイル名のみ
)

df = pd.DataFrame(dict(a=[1,2,3]))
df['b']

f:id:vaaaaaanquish:20171214060800p:plain

これくらい分かれば何とかなるなって気もしなくもない。
何より慣れたpandasの30行近いエラーがこれに収まるなら良い。
基本的にはこれで作業して、backtrace.unhook() するのが良さそう。


また、sys.exc_info()の返り値を渡す形にすればコンソール、IPython上でも利用できる

import pandas as pd
import backtrace
import sys

try:
    df = pd.DataFrame(dict(a=[1,2,3]))
    df['b']
except:
    tpe, v, tb = sys.exc_info()
    backtrace.hook(reverse=True, strip_path=True, tb=tb, tpe=tpe, value=v)

ただし、中身がcoloramaでカラーリングしてるので色は変わらない。
この辺colored-tracebackに修正していけばかなり使えそう。

 

better-exceptions

Exceptionを見やすくするパッケージ。

github.com

エラー発生時に変数に格納されている値やClassをちゃんと出してくれて見やすい(?)
https://github.com/Qix-/better-exceptions/raw/master/screenshot.png


導入は以下

pip install better_exceptions

export BETTER_EXCEPTIONS=1  # Linux / OSX
setx BETTER_EXCEPTIONS 1       # Windows


試しにこんな感じのをやってみる

import better_exceptions

def zero(x):
    y = x/0
    return y

t = zero(10)
print(t)

ちなみにスクリプト実行時のみなので、対話コンソール上やIPythonでは現状使えない。
f:id:vaaaaaanquish:20171214100150p:plain:w500
それっぽい

しかしここからpandasのExceptionの方に適応すると以下
f:id:vaaaaaanquish:20171214101256p:plain:w400
厳しい

複雑になるとちょっと厳しいものがある。

 

TBVaccine

上記better-exceptionsよりちょっと見やすいやつ

github.com

https://github.com/skorokithakis/tbvaccine/raw/master/misc/after-vars.png

導入はよしなにpipで入れてTBVACCINE変数を設定しておくか、明示的にimportする。

pip install tbvaccine
export TBVACCINE=1

 
上記Githubの画像では設定された変数の中身まで出してるけど、以下のようにshow_varsをFalseにしておけば出ない。

import tbvaccine
tbvaccine.add_hook(isolate=False, show_vars=False)
import pandas as pd

df = pd.DataFrame(dict(a=[1,2,3]))
df['b']

f:id:vaaaaaanquish:20171214111250p:plain
良い感じである。


上記したsysモジュールっぽくも使えるのも良いところ。

from tbvaccine import TBVaccine
try:
    x = 1 / 0
except:
    print(TBVaccine(isolate=False, show_vars=False).format_exc())

これならxonshでも動くし、良い感じかもしれないと思っている。
IPythonでも動くし良さ。
 
 

その他調べたやつ

tracebackturbo

雰囲気はTBVaccineっぽい。変数の中身まで見たい時と見たくない時があるよなあって思うけど消せなさそう。
github.com
3系はこっち :
GitHub - cxcv/python-tracebackturbo3: A drop-in replacement for the python3 traceback module that enables dumping of the local variable scope aside normal stack traces.

 

git-stacktrace

pinterestのgitリポジトリはじめて見た。
stacktraceと一緒に、問題が発生した箇所のGitの修正履歴をコミット単位で表示してくれる。
github.com
毎日Git使ってたら便利なのかも。
今回は趣向と外れすぎたのでノータッチ。

 

python-tblib

tracebackをPickleで固めてraiseしていくやつ。
multiprocessで処理をした時のtracebackが見やすくなる。
github.com
今回は趣向と外れすぎたのでノータッチ。いつか触る。

 

Skip-Traceback(jupyter)

jupyterの拡張でトレースバックを表示せずエラーの種類とメッセージのみ表示するやつ。
github.com
今回は趣向と外れすぎたのでノータッチ。

IPythonならまだmagic commandのdebugやpdbデバッグする方が普通に良い気がする。
JupyterまたはiPython Notebookでデバッグをする方法 - Qiita
IPython の豊富な機能を使いこなす (2) - Qiita

IPythonならultratb.ColorTBも使えるけどなあ…
Module: core.ultratb — IPython 3.2.1 documentation


 

- おわりに -

結論私の求めていた、短く良い感じにExceptionを表示する方法はbacktraceかTBVaccine辺りだろう。

個人的にbacktraceの表示形式が好きなので、ここを起点にxontribを作っていくぞという気持ち。

IPythonにも活用できたら、Jupyter notebookでミスって実行しちゃった時に出る長いエラーとおさらば出来る気がする…


 
//--- 以下参考 ---
29.9. traceback — Print or retrieve a stack traceback — Python 3.6.4 documentation
traceback — Exceptions and Stack Traces — PyMOTW 3
__exit__ must accept 3 arguments: type, value, traceback — Python Anti-Patterns documentation

PythonユーザのためのJupyter[実践]入門

PythonユーザのためのJupyter[実践]入門

blog.dscpl.com.au
d.hatena.ne.jp


 
追記:
xonshに導入する記事も書きました
vaaaaaanquish.hatenablog.com
なんとかしてJupyter notebookもbacktraceにできないかなあと思っています。

xonshにおけるxontribの紹介

- はじめに -

この記事は、Xonsh Advent Calendar 2017 - Qiita 14日目の記事です。

xonshにおけるいわゆる拡張機能であるところのxontribについて書いていきます。

「オススメXontrib!」と行きたい所ですが、そもそも2017年末時点で公開されているxontribは少ないのでほぼ全てです。

 

- xontribとは -

xontribはxonshの拡張群です。

例えばhogeパッケージをロードするには、以下Commandを入力します。

xontrib load hoge

~/.xonshrcに上記Commandを直接書くか、~/.config/xonsh/config.json内にあるxontribsという名前のlistにパッケージ名を書いておくと、xonshセッション起動時にロードされます。

手前味噌ですが、私も作っています。


現行のxontribパッケージについては、公式ドキュメントにまとまっています。
Xontribs — xonsh 0.9.11 documentation
この中にあるやつないやつで、少しだけ便利になるやつだけ紹介していきます。

[2019/06/29] 続編で作る側になろうという記事も書きました
vaaaaaanquish.hatenablog.com


 

- 補完 -

apt_tabcomplete

apt-getコマンドをtabで補完できるようにします

# 導入
pip install xonsh-apt-tabcomplete
xontrib load apt_tabcomplete

apt-getのinstall、remove、apt-cacheのsearchの補完がサポートされています。
github.com

Ubuntuなどaptを良く使う環境でxonshを使う時は入れておくと良いと思います。

 

docker-tabcomplete

dockerコマンド周りをtabで補完できるようにします

# 導入
pip install xonsh-docker-tabcomplete
xontrib load docker_tabcomplete

docker imageの補完などがxonsh上で上手くいかないのですが、こちらを導入することで解決します。
github.com

dockerで複数のコンテナを扱いながら開発している場合に便利です。

 

scrapy-tabcomplete

scrapyコマンド周りをtabで補完できるようにします

# 導入
pip install xonsh-scrapy-tabcomplete
xontrib load  scrapy_tabcomplete

scrap crawlコマンドやscrapy checkコマンドの結果のcacheから、次のコマンドを補完してくれます。

github.com

scrapyをコンソール上で使っている人(そんな人が居るかどうかは別として)には使えると思います。

 

fzf-widgets

sshコマンドの補完、historyの検索をサポートしてくれます。

公式のGIFが大体全てを説明してくれています。
https://raw.githubusercontent.com/shahinism/xontrib-fzf-widgets/master/docs/cast.gif

# 導入
pip install xontrib-fzf-widgets
xontrib load fzf-widgets

github.com

historyや補完はもともと強力なxonshですが、sshのconfigから補完は一応デフォでやってくれないので便利。

 

thefuck

コマンドを打ち間違えた時に「fuck」と入力すればコマンド候補を入力してくれるxontrib。
xonshrcの書き方の記事にもある$SUGGEST_COMMANDSをもうちょっと便利にする感じ。

# 導入
pip install xontrib-thefuck
xontrib load thefuck

xonsh記事ではないけど以下見ると大体の概要がわかります
thefuckのインストール方法 - Qiita

github.com


  

- UI -

powerline

おなじみpowerlineのサポートxontribです
見た目を色々できます

https://github.com/santagada/xontrib-powerline/raw/master/screenshot.png

# 導入
pip install xontrib-powerline
xontrib load powerline

プロンプトの右側を $PL_PROMPT、下側を $PL_TOOLBAR で設定します。
whoやbranchなどはセクションです。
pl_available_sectionsコマンドで全ての使えるセクションを表示できます。pl_build_promptコマンドで再ロードします。

$PL_PROMPT = '!'    # 使わない時
$PL_TOOLBAR = 'who>time'

xonshrcの書き方の記事でも紹介した $PROMPT も使えます。

github.com

iterm2ではpowerlineフォントが化けたり、表示が重なったりします。
その時は以下で対応できます。
iTerm2+powerline文字化け対策めも - Qiita

一回やってしまえばカッケーコンソールでxonshできます。

// 追記:2019/09/15
現在開発が止まっているようでした(開発者とも連絡がつかない状態)。私が別途xontrib-traceback2というのを作っています。
github.com

 

prompt-vi-mode

promptの表示に「vi-modeかどうか」を加えます。

# 導入
pip install xontrib-prompt-vi-mode
import xontrib.prompt_vi_mode

導入後は、xonshrcの書き方の記事でも出た、$PROMPTや$RIGHT_PROMPTに "{vi_mode}" もしくは "{vi_mode_not_input}"を突っ込むだけです。

こんな感じ
f:id:vaaaaaanquish:20171213175148p:plain

github.com

仕方ないですがimportなところに注意です。
私はptkのショートカットを登録してINSERTとNORMALを切り替えています。
vim開けばよくね…」と言われればそれはそう。


 

- タスク、操作 -

z

ディレクトリ移動を潤滑にするxontrib。
/home/work に行ったことがあれば $ z work とだけ入力すれば高確率で移動できる。

# 導入
pip install xontrib-z
xontrib load z

基本的にはcacheされたとこに行きます

github.com

アドベントカレンダー内の以下でも紹介されていて、zshのz.shを上手く書き換えているので一読すると吉です。
qiita.com

 

autoxsh

.autoxsh」というスクリプトファイルをディレクトリに設置しておくと、そのディレクトリにcdした時に毎回そのスクリプトが実行されるようになります。

# 導入
pip install xonsh-autoxsh
xontrib load autoxsh

初回の実行時には以下autoxshを許容し実行するかのメッセージが出るのでYesで実行

Unauthorized ".autoxsh" file found in this directory. Authorize and invoke? (y/n/ignore): y

github.com

.autoxshは普通のxonshスクリプトが記述できるので、lsコマンドの自動実行や、注意喚起のprint、envの切り替えを記入しておくと便利です。

 

schedule

スケジューラです。
コマンドにtimeオブジェクトを投げておくと、指定時間に実行したり、delayをかけて実行したりできます。

# 導入
pip install xontrib-schedule
xontrib load schedule

以下scheduleパッケージwrapperです
schedule — schedule 0.4.0 documentation

import time

def func():
    print("20秒経ちました")

schedule.when(time.time()+20).do(func)

時間指定のwhenと、遅延実行のdelayが使えます。

github.com

これに似た方法を上手く使えば「xonshrcに書いておいて最速でxonshを起動しつつ、よく使うパッケージを遅れて読み込む」なんてこともできそうです。いつかやりたい。


 

- おわりに -

その他にも、xonsh用のenvである"vox"用のxontribxonsh上でcondaを扱うためのxontrib簡易エディタxoを提供するxontribコマンドを保存していくxontrib…などが使えそうです。

voxはまだ使った事無く、anacondaは宗教上無理。エディタはvimだしな…となって今の記事に収まっています。


Githubリポジトリを見てもらえば分かりますが、xontribは数行Pythonを書けばできてしまいます。
xonshでは実際色んなイベントをデコレータを付けることでキャッチできるので、自身でxontribを作っていくのもかなり簡易だと感じます。
 

xonsh アドベントカレンダーも後半戦に入り、空きが出てきたのでなんとか登録だけでもお願いします!
後半戦のどこかでxontrib作れたらいいなと思っています。

qiita.com

xonshrcを書く

- はじめに -

この記事は、Xonsh Advent Calendar 2017 - Qiitaの9日目の記事です。

1日目にXonshを勧める記事を書いて「アドベントカレンダーでxonshrcのオススメ設定が出揃う」と言ったものの、なかなかそれらしい記事が出てこないので書いておきます。


Python Prompt Toolkitが使えるなら、アドベントカレンダー内の以下の記事もオススメです。
qiita.com


追記2018/06/22:
さらにちゃんとまとめました
vaaaaaanquish.hatenablog.com
vaaaaaanquish.hatenablog.com


追記2019/08/28
さらに最新のxonsh 0.9.10のEnvを全て実装付きでまとめました
こちらのほうが参考になると思います。
vaaaaaanquish.hatenablog.com

 

- 私のxonshrc -

先に結論として設定している最もシンプルな部分のrcを出しておきます。
xonshrcを制御するxonsh/configファイルについては基本的にはdefaultのものを利用しています。

# vi風の操作
$VI_MODE = True
# Ctrl + D で終了しない
$IGNOREEOF = True
# tabではなく空白4つ
$INDENT = "    "
# 補完時に大小区別しない
$CASE_SENSITIVE_COMPLETIONS = False
# 補完選択Enterで即実行しない
$COMPLETIONS_CONFIRM = True
# 連続重複コマンドを保存しない
$HISTCONTROL = (ignoredups)
# 括弧を補完
$XONSH_AUTOPAIR = True
# ディレクトリ名を入力すればcdできる
$AUTO_CD = True
# エラー全て吐くように
$XONSH_SHOW_TRACEBACK = True
# プロンプトの設定タイムアウトのメッセージ抑制
$SUPPRESS_BRANCH_TIMEOUT_MESSAGE = True

# 見た目ウザかったら切か変更する
# 参考:http://xon.sh/tutorial.html#customizing-the-prompt
# プロンプト上には場所だけ出す
$PROMPT = "{INTENSE_YELLOW}[ {cwd} ] {GREEN}$ "
# 下部にuser, host情報を追加
$BOTTOM_TOOLBAR = "{BACKGROUND_WHITE}{BLACK}{user}@{hostname}"
# 右にbranch情報
$RIGHT_PROMPT = "{curr_branch}"
# lsコマンドの結果の見た目
$LS_COLORS="di=34:ln=35:so=32:pi=33:ex=31:bd=46;34:cd=43;34:su=41;30:sg=46;30:tw=42;30:ow=43;30"

#えいりあす
aliases["lt"] = "ls -ltr"
aliases["l"] = "ls -ltr"
aliases["la"] = "ls -la"
aliases["ll"] = "ls -l"
aliases["so"] = "source"
aliases["v"] = "vim"
aliases["vi"] = "vim"
aliases["vx"] = "vim ~/.xonshrc"
aliases["vv"] = "vim ~/.vimrc"
aliases["vs"] = "vim /etx/ssh/ssh_config"
aliases["cp"] = "cp -i"
aliases["rm"] = "rm -i"
aliases["ju"] = "jupyter notebook"

見た目はこんな感じ

f:id:vaaaaaanquish:20171209154013p:plain

これだけで大体良い感じですが、Pythonで関数を作ってそこにaliasを貼ることももちろんできるので、加えて幾つか記述していますがそれらについてはまた別途。


 

- xonshrcで使える環境変数まとめ -

以下ではxonshrcで使える環境変数をまとめておきます。
基本的には、以下Referenceに書いてある内容です。
Environment Variables — xonsh 0.9.11 documentation

  

見た目、表示系

    # カラーテーマ
    # 自分はdefaultでiTerm2側の設定を利用
    # xonfig styles コマンドで全てのスタイル表示
    # 良さげなのだと'native'
    $XONSH_COLOR_STYLE = ‘default’

    # 下部に表示するツールバー
    # prompt-toolkitが導入されているShellのみ
    $BOTTOM_TOOLBAR

    # Shell上のシンタックスハイライト強調表示
    $COLOR_INPUT = True

    # コマンド実行の戻り値にもシンタックスハイライト
    $COLOR_RESULTS = True

    # インデント文字列
    # 空白4つとかTabとか任意に
    $INDENT = "    "

    # 改行した時にでてくる左端の文字
    # 指定された長さ以下だった場合は繰り返し表示される
    $MULTILINE_PROMPT = "."

    # 右揃えで表示する文字列
    $RIGHT_PROMPT = ""

    # プロンプトのサブプロセス、書式設定などのタイムアウト
    $VC_BRANCH_TIMEOUT = 0.1

    # プロンプトの設定タイムアウトのメッセージ抑制
    $SUPPRESS_BRANCH_TIMEOUT_MESSAGE = False

    # MercurialのbranchをTerminalに表示するか
    $VC_HG_SHOW_BRANCH = True

    # datetime.strptime()で出せる時間の表示の仕方
    $XONSH_DATETIME_FORMAT = ‘%Y-%m-%d %H:%M’

    # xonshの見た目の色々、文字のシンボル
    # http://xon.sh/envvars.html#xonsh-gitstatus
    $XONSH_GITSTATUS_*

    # エラーが出たときトレースバックを表示するかどうか
    $XONSH_SHOW_TRACEBACK = False

    # defaultプロンプトの見た目設定
    # http://xon.sh/tutorial.html#customizing-the-prompt
    $PROMPT = xonsh.environ.DEFAULT_PROMPT

    # titleとpromptのカスタマイズ
    # http://xon.sh/tutorial.html#customizing-the-prompt
    $PROMPT_FIELDS = xonsh.prompt.PROMPT_FIELDS

    # PROMPTのエラー表示版
    $XONSH_STDERR_POSTFIX
    $XONSH_STDERR_PREFIX

 

操作

    # マウス操作で履歴戻ったりするかどうか
    # prompt_toolkitが導入されているShellのみ
    $MOUSE_SUPPORT = False

    # Ctrl + D で終了しない
    $IGNOREEOF = False

    # vi風の操作
    $VI_MODE

 

補完

    # キー入力ごとに評価する
    $UPDATE_COMPLETIONS_ON_KEYPRESS = False

    # プロンプト間でコマンドをcacheしない設定にするか
    # Trueにすると毎回キー入力待ち再評価が発生する
    $UPDATE_PROMPT_ON_KEYPRESS = False
    
    # 補完時に大文字小文字区別するか
    # LinuxだとdefaultでTrue
    # 個人的にはFalse派
    $CASE_SENSITIVE_COMPLETIONS

    # コマンド履歴の保存の仕方
    # ignoredupsは重複コマンドを保存しない
    # ignoreerrは失敗したコマンドを保存しない
    $HISTCONTROL = (ignoredups, ignoreerr)

    # 補完時に[]と()を考慮する
    $COMPLETIONS_BRACKETS = True

    # ユーザ確認する前に補完を表示する上限数
    $COMPLETION_QUERY_LIMIT = 100

    # Tab補完メニューで表示する行数
    # prompt-toolkitが導入されているShellのみ
    $COMPLETIONS_MENU_ROWS = 5

    # Tab補完メニューが表示されている時にEnterで即実行するか
    # Trueなら実行しない
    # prompt-toolkitが導入されているShellのみ
    $COMPLETIONS_CONFIRM = False

    # Tab補完のPathのsubsequence matching
    # ~/u/ro で ~/lou/carcolh をよしなに補完してくれるみたいなやつ
    $SUBSEQUENCE_PATH_COMPLETION = True

    # Pythonスクリプトの補完
    # 基本的にdefaultがサイコー
    # prompt-toolkitが導入されているShellのみ
    $COMPLETIONS_DISPLAY = multi

    # あいまい補完をするか
    $FUZZY_PATH_COMPLETION = True

    # 補完サジェストを出す
    # defaultでTrue
    $AUTO_SUGGEST_IN_COMPLETIONS = True

    # 補完サジェストを 右矢印キーで入力
    # $SHELL_TYPE=prompt_toolkitが指定されている必要あり
    # defaultがTrueなのでptkが入っていればデフォで動いてるはず
    $AUTO_SUGGEST = True

    # BASHの補完機能をそのまま利用するための
    # defaultでは以下、tupleを指定する
    $BASH_COMPLETIONS = ('/usr/share/bash-completion/bash_completion', )

    # パターンマッチなどglobした時に結果をソートするか
    $GLOB_SORTED = True

    # 実行可能なファイルの拡張子
    # 大文字で書く必要がある
    $PATHEXT = [".EXE"]

    # 変なコマンドを入力したら もしかして を出す
    $SUGGEST_COMMANDS = True

    # もしかして最大数
    $SUGGEST_MAX_NUM = 5

    # もしかしてサジェストの誤字数
    $SUGGEST_THRESHOLD = 3

    # 出力後に新しい空行を追加するか
    $XONSH_APPEND_NEWLINE = False

    # [], ()などの括弧を補完するか
    $XONSH_AUTOPAIR = False

    # 全てのコマンド実行をcacheするか
    $XONSH_CACHE_EVERYTHING = False

    # コードをcacheしておくか(T)、毎回コンパイルして実行するか(F)
    $XONSH_CACHE_SCRIPTS = True

 

ディレクトリ移動

    # ディレクトリ名を入力すればcdできる
    $AUTO_CD = False

    # ディレクトリ移動したらスタックにpush
    # dirstack実装 : https://pypkg.com/pypi/xonsh/f/xonsh/dirstack.py
    $AUTO_PUSHD = False

    # pushdした時にdirで現在のスタック内容を表示しない
    $PUSHD_SILENT = False

    # dirstack最大数
    $DIRSTACK_SIZE = 20

    # CD時にshとのroot関係が壊れないようにPATH指定するやつ
    # zshでいうこれ : https://robots.thoughtbot.com/cding-to-frequently-used-directories-in-zsh
    $CDPATH = ("/", )

    # cwdコマンドで指定したディレクトリに向けたショートカット
    $DYNAMIC_CWD_ELISION_CHAR = "/"

    # cwdプロンプト変数の文字数の指定
    $DYNAMIC_CWD_WIDTH = (inf, ‘c’)

 

変数、設定

    # PYTHON_PATH
    $PATH = (...)

    # 前回の作業ディレクトリ
    $OLDPWD

    # サブプロセスモードで文字列内の環境変数を展開するか
    $EXPAND_ENV_VARS = True

    # xonshの場所
    $XONSH_CONFIG_DIR = $XDG_CONFIG_HOME/xonsh

    # 外部のaliasを優先するかどうか
    # $XONSH_CONFIG_DIR/config.jsonに書くのでxonshrcに書いても意味はない
    $FOREIGN_ALIASES_OVERRIDE = False

    # $XONSH_CONFIG_DIR/config.jsonが読まれたかどうかがboolで入ってるので判定用
    $LOADED_CONFIG

    # 読見込まれたRCが動いているかのbool値のlist
    $LOADED_RC_FILES = (~/.xonshrc)

    # 連続でコマンド実行した時にスリープする秒数
    $XONSH_PROC_FREQUENCY = 0.0001

    # pretty printingの戻り値があるかどうか
    # import pprintしたりするなら
    $PRETTY_PRINT_RESULTS

    # configファイルへのPATH
    $XONSHCONFIG = $XONSH_CONFIG_DIR/config.json

    # rcファイルへのPATH
    $XONSHRC = ['/etc/xonshrc', '~/.xonshrc']

    # デスクトップ標準のディレクトリ
    $XDG_CONFIG_HOME = ~/.config

    # デスクトップ標準のデータディレクトリ
    $XDG_DATA_HOME = ~/.local/share

    # 実際のxonshデータが入っている場所
    $XONSH_DATA_DIR = $XDG_DATA_HOME/xonsh

    # ディレクトリプッシュがされているかどうかのフラグ
    $PUSHD_MINUS

    # xonsh環境が変化したかのフラグ
    $UPDATE_OS_ENVIRON

    # python実行環境へのPATH
    $VIRTUAL_ENV

    # インタラクティブ実行で動いてるかどうかのフラグ
    $XONSH_INTERACTIVE

    # xonshがログインシェルになっているかどうかのフラグ
    $XONSH_LOGIN

    # xonshスクリプトを実行している場合、そのスクリプトへの絶対Path
    $XONSH_SOURCE

 

Windows向け

    # Windowsのanxicon用
    $ANSICON = False

    # 補完した時に"/"を強制的に使用
    $FORCE_POSIX_PATHS = False

    # cmd.exe上で色を変える
    $INTENSIFY_COLORS_ON_WIN = True

    # Winでユニコード使う
    $WIN_UNICODE_CONSOLE

 

その他

    # readline, prompt_toolkit, random, best
    # 基本的にはptlが使われてそれ以外の状況ではよしなに選択してくれるbestでOK
    $SHELL_TYPE = best

    # プロンプトのタイトル
    # http://xon.sh/tutorial.html#customizing-the-prompt
    $TITLE = xonsh.environ.DEFAULT_TITLE

    # 文字コード
    $LANG = ‘C.UTF-8# サブプロセスが利用するエンコーディング
    $XONSH_ENCODING

    # エンコーディングエラーを処理するフラグ
    # https://docs.python.org/3/library/codecs.html#error-handlers
    $XONSH_ENCODING_ERRORS = surrogateescape

    # サブプロセスでエラーが出た時終了させるかどうか
    # xonshプロンプト上ではあまり良くないが、 xonshスクリプトを実行する時効果あり
    # subprocess.CalledProcessErrorがraiseされる
    $RAISE_SUBPROC_ERROR

    # historyでjson以外にsqliteが選べる
    $XONSH_HISTORY_BACKEND = ‘json’

    # historyファイルの保存先
    $XONSH_HISTORY_FILE = ~/.xonsh_history

    # コマンドやファイルのhistory保存数
    # http://xon.sh/envvars.html#xonsh-history-size
    $XONSH_HISTORY_SIZE = (8128, 'commands')

    # Standard I/OをHistoryに保存するか
    $XONSH_STORE_STDIN
    $XONSH_STORE_STDOUT

    # 端末エミュレータによるTerminalの設定色々。基本触る機会はない
    $TERM

    # エラーのトレースバックログを保存するPathの指定
    $XONSH_TRACEBACK_LOGFILE = "/"

    # デバッグ機能、数値に応じて以下が出てくる
    # 1, 重複importの抑制情報
    # 2, 入力変換、コマンド置換の情報
    # 3, PLYデバッガによる解析結果
    $XONSH_DEBUG = 0


 

- おわりに -

ひとまずまとめて終わりです。

またアドベントカレンダー内に空きがあれば自作のrcに書いている関数群を出していこうと思います。

誰か今からpip installして埋めてくれてもいいのよ!

qiita.com


 

xonshの過去のコマンド履歴を可視化する

- はじめに -

この記事は、Xonsh Advent Calendar 2017 - Qiitaの6日目の記事です。

せっかくxonshではMatplotlibが使えたりするので、Command履歴の分析等の補助をする関数を書いてメモしておきたいと思います。


 

- xonshのhistory -

xonshの過去の入力履歴は以下で呼び出す事ができます。

__xonsh_history__

実際には、xonshのコマンド履歴は以下のようにSession毎にjsonファイルで管理されています。

history file
# ~/.local/share/xonsh/xonsh-7305fb13-34ea-44fd-9845-197fe015359d.json

さっそくの余談ですが、このバックエンドにsqliteを指定する事も可能です(Tutorial: History — xonsh 0.6.0.dev151 documentation)。


__xonsh_history__で得られるxonsh.history.json.JsonHistoryオブジェクトは、Commandの履歴を取得するためのメソッドを2種類持っています。

__xonsh_history__.items()    # 今のSessionでのHistory
__xonsh_history__.all_items()    # 過去全てのSessionでのHistory

どちらもIteratorが返ってくるので、例えば現在過去全てのコマンド履歴を取得するには以下のように

for x in __xonsh_history__.all_items():
    print(x["inp"])

Reference : History Backend JSON – xonsh.history.json — xonsh 0.6.0.dev151 documentation


また、jsonを直接読みにいく事ももちろんでき、xonshのhistory.jsonを直接読みに行くスクリプトは、アドベントカレンダー内の以下の記事でも既に書かれています。

qiita.com


 

- 過去のコマンドを可視化 -

以下の記事でも利用したitermplotパッケージを利用して、iTerm2上にインラインに表示してみます。
導入や綺麗な表示の仕方は以下記事で。
(以下記事内にiTerm2以外の方法も一応記述しています)
vaaaaaanquish.hatenablog.com


空白で区切られた最初のコマンド部分が10文字以下のものをCounterに投げてMatplotlibに可視化します。

import itermplot
import matplotlib.pyplot as plt
import numpy
from collections import Counter

data = Counter([x["inp"].split(" ")[0] for x in __xonsh_history__.all_items() if len(x["inp"].split(" ")[0])<10])
labels, values = zip(*data.items())
indexes = np.arange(len(labels))

plt.figure(figsize=(20,10))
plt.bar(indexes, values, width)
plt.xticks(indexes, labels, rotation='vertical')
plt.show()

結果がxonshコンソール上で見えます
f:id:vaaaaaanquish:20171206225049p:plain


ちょっと業務で使ったものを削った結果を出していますが、これは恥ずかしいですね。

xonshなのでimportやforが多いのは当たり前ですが、ls しすぎだし、X, Yといった変数を使っていたり、果にはexitもかなり使っています。xonshが嫌いなのでしょうか。
これがまだ手元のMacなので良いですが、普段はリモートサーバで作業しているので、そちらではより酷いものが見れると思います(公開はしないですが)。


しかし、これでlsというコマンドにめちゃくちゃ時間を取られている事が分かりました。lsは基本的には自動的に発動するようにしていくのが吉という事ですね。

 
 
ついでなので最も多いimportから、何を多く使っているか見てみます。

data取得箇所を変えてやれば実現できそうです。

data = Counter([x["inp"].split(" ")[1] for x in __xonsh_history__.all_items() if x["inp"].split(" ")[0]=="import"])

 
import time, plt, mathが多そう。xonshrcに書いておいて事前importするようにすれば私の仕事も減りそうです。
f:id:vaaaaaanquish:20171206230255p:plain


 

- おわりに -

ギリギリ滑り込みセーフで記事を書きましたが何とかなりました。

もうちょっと分析らしいところまで行きたかったのですが、(見せてもOKそうな)Historyが以外に少なく、時間も微妙だったので断念しました。
まあでもギリギリでも可視化まで出来たのは、Pythonという言語がシェルで扱える事の利点でもあるなと思いました。

今まで使っていたzshのログのconvertとか前後のコマンドを確認とかまでやれれば良かった…が、アドベントカレンダーに空きを見つけて書きたいと思います。


それでも全然空きがありそうなので、xonsh試したい方でも是非ご参加下さい!!

qiita.com


 

Xonshでmatplotlibグラフをコンソールにインライン描画してメモリ状況を観察する

- はじめに -

この記事はXonsh Advent Calendar 2017 4日目の記事です。

Xonshの中にはxontribというメソッド群が存在します。
その中のmplhooksは、画像の描画をサポートしてくれます。

本記事では以下について記述します

  • xontrib.mplhooksを利用したxonshコマンドプロンプト上へのMatplotlibグラフのインライン描画
  • vm_statコマンドを利用したMac OS Xのメモリ状況の取得とPythonによるparse
  • Xonshでメモリ状況のリアルタイム可視化

以下アジェンダです


 

- Xonshのグラフインライン描画 -

Xonshでグラフは端末状にインライン描画する方法は、以下の2種類が容易されています。

  • xontrib.mplhooks.display_figure_with_iterm2(fig)
  • xontrib.mplhooks.show()

以下がソースコードです。
xontrib.mplhooks — xonsh 0.5.12.dev97 documentation
xonsh/mplhooks.py at master · xonsh/xonsh · GitHub
Pythonなのですぐ読めます。

display_figure_with_iterm2は、 iterm2_toolsを使ってiTerm2上にグラフを描画するようになっています。
showは、iterm2_toolsがimportできればdisplay_figure_with_iterm2で描画、iterm2_toolsが存在しない場合はplt.gcf()で現状プロット図を取得してきてRGB値をcolor stringにして描画する形になっています。

iTerm2でインライン描画

私はiTerm2利用者ですので以下でiterm2_toolsを導入しています。

pip install iterm2_tools

GitHub - asmeurer/iterm2-tools: iTerm2 tools for Python

以下エラーが出るのでXonshコンソールを再起動しておきます。

NameError: name 'display_image_bytes' is not defined

再起動後、Xonshコンソール上で以下を淡々と入力していきます。
もしくは適当にxshスクリプトにしてもOKです。

import matplotlib.pyplot as plt
import xontrib.mplhooks
import numpy as np
x = np.linspace(-3, 3, 20)
y = x ** 2
fig = plt.figure(figsize=(6, 4))
ax1 = fig.add_subplot(1, 1, 1)
ax1.plot(x, y)
xontrib.mplhooks.display_figure_with_iterm2(fig)

f:id:vaaaaaanquish:20171121140615p:plain:w350
xonsh上でもインライン描画できました

 

Terminal.app上のxonshでインライン描画

試しにMacにデフォルトで付随するTerminal.appでもXonsh上でインライン描画をしてみます。

上記iTerm2の時と同じGraphを作って以下。

xontrib.mplhooks.show()

f:id:vaaaaaanquish:20171121141037p:plain:w350
ちょっとアレですがまあまあ…

iTerm2の描画は良いですね

 

itermplotでインライン描画

上記2つに加えてiTerm2にはインライン描画パッケージとして、itermplotというのがあります。
GitHub - daleroberts/itermplot: An awesome iTerm2 backend for Matplotlib, so you can plot directly in your terminal.

Animation supportもされており、インラインでリアルタイム表示ができそうです。
(といっても事前にGIF画像にしてiTermの表示機能に送る形ですが…)

導入はpipで、xonshを起動する前にMPLBACKENDにitermplotを指定しておくとplt.show()がインライン描画になります。

pip install itermplot
export MPLBACKEND="module://itermplot"

また、後述しますがリアルタイム表示するにはImageMagickが必要なのでインストールしておきます。

brew install imagemagick

sampleスクリプトを動かしてみます。

import matplotlib.pyplot as plt
plt.plot([1,2,3])
plt.show()

f:id:vaaaaaanquish:20171202214602p:plain:w350
いちばんつよそう

背景が黒い場合は以下で良さげな表示に

export ITERMPLOT=rv


  

- vm_statコマンドでメモリ可視化 -

Macでメモリ状況を出力するコマンドではfreeやtop、vm_statといった選択肢があります。

今回は適当にvm_statを選択します。
Xonshであれば以下コマンドどちらも結果を得ることができます

vm_stat
$(vm_stat)    # python string

vm_statコマンドの結果をparseしてdictにする関数を作って実行してみます。

def vms():
    vm_dic = {}
    for i,x in enumerate($(vm_stat).split("\n")):
        row = x.split(":")
        if i>1 and len(row)==2:
            vm_dic[row[0]] = int(row[1].strip().replace(".",""))*4096/(1024.0 ** 3)
    return vm_dic

以下のようにGB単位でメモリ状況が出力されます。

$ vms()
{'"Translation faults"': 2588.0195083618164,
 'Anonymous pages': 7.266414642333984,
 'Compressions': 54.13356018066406,
 'Decompressions': 33.81982421875,
 'File-backed pages': 2.6624984741210938,
 'Pageins': 32.48748779296875,
 'Pageouts': 0.26355743408203125,
 'Pages active': 4.984306335449219,
 'Pages copy-on-write': 82.98124694824219,
 'Pages inactive': 4.944099426269531,
 'Pages occupied by compressor': 3.5574417114257812,
 'Pages purgeable': 0.14667129516601562,
 'Pages purged': 1.3369598388671875,
 'Pages reactivated': 22.909423828125,
 'Pages speculative': 0.000507354736328125,
 'Pages stored in compressor': 8.809425354003906,
 'Pages throttled': 0.0,
 'Pages wired down': 2.446277618408203,
 'Pages zero filled': 1392.7644996643066,
 'Swapins': 16.50379180908203,
 'Swapouts': 17.286388397216797}

 
せっかくですのでwatchコマンドのように、メモリ状況をリアルタイムで表示し続けてみます。

import json
while True:
    vm_dic = vms()
    $[clear]
    print(json.dumps(vm_dic, indent=4))

f:id:vaaaaaanquish:20171121144149g:plain:w400

watch vm_statでよくない!?みたいな何かが出来上がりました。

vms functionをprintするスクリプトにして、watchもできます。
watchコマンドはデフォルトでsh -cにコマンドを飛ばすだけなので、--exec, -xで指定できるよう環境設定するか、以下のようにxonshを定期的に作るという酷いやつで対応できます。

# mac -> $ brew install watch
watch "xonsh sample.xsh"

watch vm_statでよくない!?みたいな何かです。

  

- vm_statをMatplotlibでリアルタイム描画 -

上記2項目で作った関数を利用して、Matplotlibでvm_statの結果をリアルタイム描画してみます。

wired, active, inactive, speculative辺りがusedメモリに関連する項目ですのでそちらを可視化してみます。
(occupied by compressorも必要かな?)

スクリプトが適当です。

import time
import xontrib.mplhooks
import numpy as np
import matplotlib.pyplot as plt
plt.style.use('ggplot') 

def init_data(fig, id, title):
    x = np.zeros(100)
    y = np.zeros(100)
    subplot = fig.add_subplot(2,2,id)
    subplot.set_title(title)
    subplot.set_ylabel('GB')
    subplot.set_xlabel('time')
    li, = subplot.plot(x, y)
    return x, y, subplot, li

def set_data(x,y,vm_dic,key):
    x = np.append(x, time.time())
    x = np.delete(x, 0)
    y = np.append(y, vm_dic[key])
    y = np.delete(y, 0)
    return x,y

def update_fig(x, y, subplot, li):
    li.set_xdata(x)
    li.set_ydata(y)
    subplot.set_ylim(min(y), max(y))
    subplot.set_xlim(min(x), max(x))

def watch_vms_windows():
    fig = plt.figure()
    x,y,subplot,li = init_data(fig, 1, "Pages active")
    x2,y2,subplot2,li2 = init_data(fig, 2, "Pages inactive")
    x3,y3,subplot3,li3 = init_data(fig, 3, "Pages speculative")
    x4,y4,subplot4,li4 = init_data(fig, 4, "Pages wired down")
    count = 0
    while True:
        vm_dic = vms()
        x,y = set_data(x, y, vm_dic, "Pages active")
        x2,y2 = set_data(x2, y2, vm_dic, "Pages inactive")
        x3,y3 = set_data(x3, y3, vm_dic, "Pages speculative")
        x4,y4 = set_data(x4, y4, vm_dic, "Pages wired down")
        if count > 100:
            update_fig(x, y, subplot, li)
            update_fig(x2, y2, subplot2, li2)
            update_fig(x3, y3, subplot3, li3)
            update_fig(x4, y4, subplot4, li4)
            plt.pause(.01)
        else:
            count += 1
            print(count, end="\r")

以下参考にしています。
Arduino で測定したデータを Matplotlib でリアルタイムプロット | org-技術

100ループ回してGraph描画に十分なデータを集めたらGraph描画を開始するスクリプトです。

f:id:vaaaaaanquish:20171121144909g:plain

QuickTime Playerを利用して画面の動画を撮っているのでactiveがガンガン上昇しinactiveが下降、メモリ利用と廃棄が頻繁に行われている事がわかります。


$[clear] して display_figure_with_iterm2(fig) でインライン描画するスクリプトにする事も可能で、試しましたがdisplay_figure_with_iterm2では実行時描画になるため、思ったより綺麗に表示されませんでした。


 

- MatplotlibのAnimationモジュールを使ってリアルタイム描画を実現する -

Matplotlibにはpltで作られた配列をアニメーションにして表示するモジュールが存在する。
全てのGraphを事前に配列に取っておいて描画するArtistAnimation、動的に生成していくFuncAnimationの2つである。
animation module — Matplotlib 2.1.0 documentation

Animationはgifやmp4を生成するだけなので、これらを前述したitermplotに流してiTerm2にインライン描画する。

itermplotのカッケー背景はAnimationには対応してないので以下だけ設定しておく。

export MPLBACKEND="module://itermplot"
export ITERMPLOT_FRAMES=100

適当に書いたコード

import time
import xontrib.mplhooks
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
plt.style.use('ggplot') 

class VMS:
    def __init__(self, fig):
        self.fig = fig
        self.x, self.y, self.subplot, self.li = self.init_data(1, "Pages active")
        self.x2, self.y2, self.subplot2, self.li2 = self.init_data(2, "Pages inactive")
        self.x3, self.y3, self.subplot3, self.li3 = self.init_data(3, "Pages speculative")
        self.x4, self.y4, self.subplot4, self.li4 = self.init_data(4, "Pages wired down")
        self.c = 0
        for i in range(100):
            self.plotsub(i)
        
    def init_data(self, id, title):
        x = np.zeros(100)
        y = np.zeros(100)
        subplot = self.fig.add_subplot(2,2,id)
        subplot.set_title(title)
        subplot.set_ylabel('GB')
        li, = subplot.plot(x, y)
        return x, y, subplot, li

    def vms(self):
        vm_dic = {}
        for i,x in enumerate($(vm_stat).split("\n")):
            row = x.split(":")
            if i>1 and len(row)==2:
                vm_dic[row[0]] = int(row[1].strip().replace(".",""))*4096/(1024.0 ** 3)
        return vm_dic

    def count(self):
        self.c += 1

    def plotsub(self, frame):
        vm_dic = self.vms()
        self.count()
        # subplot1
        self.x = np.append(self.x, self.c)
        self.x = np.delete(self.x, 0)
        self.y = np.append(self.y, vm_dic["Pages active"])
        self.y = np.delete(self.y, 0)
        self.li.set_xdata(self.x)
        self.li.set_ydata(self.y)
        self.subplot.set_ylim(min(self.y), max(self.y))
        self.subplot.set_xlim(min(self.x), max(self.x))
        self.subplot.set_xticks(np.arange(min(self.x), max(self.x), 20))
        # subplot2
        self.x2 = np.append(self.x2, self.c)
        self.x2 = np.delete(self.x2, 0)
        self.y2 = np.append(self.y2, vm_dic["Pages inactive"])
        self.y2 = np.delete(self.y2, 0)
        self.li2.set_xdata(self.x2)
        self.li2.set_ydata(self.y2)
        self.subplot2.set_ylim(min(self.y2), max(self.y2))
        self.subplot2.set_xlim(min(self.x2), max(self.x2))
        self.subplot2.set_xticks( np.arange(min(self.x2), max(self.x2), 20) )
        # subplot3
        self.x3 = np.append(self.x3, self.c)
        self.x3 = np.delete(self.x3, 0)
        self.y3 = np.append(self.y3, vm_dic["Pages speculative"])
        self.y3 = np.delete(self.y3, 0)
        self.li3.set_xdata(self.x3)
        self.li3.set_ydata(self.y3)
        self.subplot3.set_ylim(min(self.y3), max(self.y3))
        self.subplot3.set_xlim(min(self.x3), max(self.x3))
        self.subplot3.set_xticks( np.arange(min(self.x3), max(self.x3), 20) )
        # subplot4
        self.x4 = np.append(self.x4, self.c)
        self.x4 = np.delete(self.x4, 0)
        self.y4 = np.append(self.y4, vm_dic["Pages wired down"])
        self.y4 = np.delete(self.y4, 0)
        self.li4.set_xdata(self.x4)
        self.li4.set_ydata(self.y4)
        self.subplot4.set_ylim(min(self.y4), max(self.y4))
        self.subplot4.set_xlim(min(self.x4), max(self.x4))
        self.subplot4.set_xticks( np.arange(min(self.x4), max(self.x4), 20) )


fig = plt.figure(figsize=(8,8))
vms = VMS(fig)
animation.FuncAnimation(vms.fig, vms.plotsub, blit=True)
plt.show()

plt.show()がitermplotによって継承されてればインラインで見れる
f:id:vaaaaaanquish:20171203221702g:plain

これは動的に見れている訳ではなく、事前に100フレーム分をgif画像にしてbyteIOでiTerm2に送り込んでいるだけなのでリアルタイムの本質を見失っている状態である。

iTerm2に表示できるGIFのフレーム数を上手くコントロールできないとか課題は多いがまあできたという事で…

 

- おわりに -

「これ watch vm_stat で良くね!?」
「リアルタイムインライン描画とは」
Pythonスクリプトなら os.system や commands.getoutput でコマンド実行できるよね!?」
「これXonshじゃなくても良くない!?」
等の耳が痛い声が聞こえて来ます。

どんどんおかしい方向に進んだ気がしますが、xonshの繁栄のための知見として納めておきます。

おわり。