- WATは、Pythonランタイム上で正体不明のオブジェクトを素早く把握するためのインスペクタで、型・値・属性・メソッド・親型・シグネチャ・ドキュメント・ソースコードまで一度に確認できる
- 基本的な使い方は
wat / objectで、wat(object)と同じように動作し、wat.short / 'foo'、'foo' | wat.short、wat('foo', short=True)のような複数の構文をサポートする
.short、.dunder、.long、.code、.caller、.public、.all、.ret、.strなどのmodifierをチェーンして、出力範囲、返却方法、カラー出力、呼び出し位置表示を調整できる
- インストールは
pip install watの後にimport watで行え、素早いデバッグのために Insta-Load スニペットをPythonセッションに貼り付ければ、同じセッション内でインストールなしでも使える
- Django
User、re.match、pathlib、colorsys.hsv_to_rgb、typing.List[str]、str | None などの例は、WATがデバッグ・REPL探索・Python内部の学習に使えることを示している
WATがすること
- WATは、Pythonオブジェクトをランタイムで探索・検査するためのツール
- 正体不明のオブジェクトが何なのか把握しにくいとき、Pythonコンソールで
watインスペクタを使ってオブジェクトの正体を調べられる
- 任意の
objectに対してwat / objectを実行すると、次の情報を確認できる
- オブジェクトの type
- 整形された値
- 変数とメソッド
- 親型
- シグニチャ
- ドキュメント
- ソースコード
- 同じ詳細検査は
wat(object)構文でも利用できる
Watは英語のwhatの変形で、混乱や不快感を表すときに使われる言葉として紹介されている
基本的な使い方と構文
- 素早い入力のために除算演算子を使う
- 同じ検査に対して複数の構文を使える
wat.short / 'foo': 素早く入力するための構文
wat.short('foo')
wat('foo', short=True): 自然なPython構文
'foo' | wat.short: Unixパイプ風の構文
wat.modifier / fooの形で検査動作を調整できる
- modifierはチェーン可能で、例として
wat.short.str.gray / 'foo'がある
- Pythonではオブジェクトはデータ構造だけでなく、関数、クラス、モジュール、組み込み型なども含むため、
watはどんなオブジェクトでも探索できる
- インタプリタで
watと入力すると、watオブジェクト自体のヘルプを見られる
Modifierで調整する検査範囲
.shortまたは.sは、オブジェクト内部の変数やメソッドのような属性を隠し、値、型、親型、シグニチャ、ドキュメントだけを出力する
.dunderは__で始まる dunder属性 を表示する
.longは省略しない値とdocstringを表示する
.codeは関数、メソッド、クラスの ソースコード を表示する
.nodocsは関数とクラスのドキュメントを隠す
.callerは検査がどのように、どこから呼び出されたかを表示し、REPLではなくファイル内で動作する
.publicはprivate属性を隠し、public属性だけを表示する
.allは可能な限りすべての情報を含める
.retは検査後にオブジェクトを再び返す
.strは出力の代わりに結果文字列を返す
.grayはコンソールのカラー出力を無効化する
.colorはコンソールのカラー出力を強制する
wat.localsはローカル変数を検査し、wat.globalsはグローバル変数を検査する
インストールとInsta-Load
- pipでのインストール手順は次の通り
pip install wat
- Pythonで
import wat
watパッケージには 外部依存関係 がない
- 素早いデバッグのため、同じPythonセッション内でインストールせずに使える Insta-Load 方式を提供している
- Insta-Loadは
base64、zlibをimportした後、圧縮・エンコードされたコード文字列を復元し、exec(..., globals())で実行するPythonスニペットをインタプリタに貼り付ける方式
- Insta-Loadスニペット実行後は
wat objectを使える
- スニペット実行前に、実行内容を検証することが推奨されている
print(zlib.decompress(base64.b64decode(code)).decode())で展開されたコード内容を事前に確認できる
inspection.pyの内容をインタプリタに貼り付けても同じ効果が得られる
- pipでパッケージをインストールしてコードを確認する方法も示されている
- WATは単一のUnicodeグリフからロードできる
- Unicode文字列ベースのローダーは、長い絵文字・結合文字列を
ord(c) & 255でバイト列化し、zlib.decompress(...)の後にexec(...)で実行する形になっている
オブジェクト型と使い方の把握
- 動的型付け言語であるPythonではオブジェクト型を把握しにくいことがあり、WAT Inspectorは型名と、その型が属するモジュールを表示する
- 型確認の例では、値、型、長さを合わせて表示する
wat.short / (1,)は値(1,)、型tuple、長さ1を出力する
wat.short / {None}は値{None}、型set、長さ1を出力する
- Django
Userオブジェクトの例では、wat.short / userがstr: admin、repr: <User: admin>、型django.contrib.auth.models.User、親型の一覧を出力する
- 実際の型を確認した後でコードに型アノテーションを入れると、その後の混乱を減らせる
- 正体不明のオブジェクトの使い方を把握したいときは、メソッド一覧、シグニチャ、docstringを出力できる
- 例として
wat / ['foo']が示されている
- docstring全体を見たいなら
wat.longを使う
- 関数の使い方を把握するために、関数のdocstringとシグニチャを見られる
- 例として
wat / str.splitが示されている
属性・モジュール・ソースコードの探索
- 検査対象オブジェクトの内部を確認するために、属性 と各属性の型を一覧表示できる
- 例として
wat / re.match('(\\d)_(.*)', '1_title')が示されている
- モジュール探索用途にも使え、選んだモジュールの関数、クラス、サブモジュールを一覧表示できる
import pathlibの後にwat / pathlibを実行する例がある
- その後
wat / pathlib.fnmatchのようにさらに深く探索できる
- WAT Inspectorはデフォルトで
__で始まる属性を隠す
wat.dunder / {}でdunder属性を見られる
- 関数が実際にどう動くのか確認するために、ソースコードを見られる
import colorsysの後にwat.code / colorsys.hsv_to_rgbを実行する例がある
- 入れ子になったdictやlistは、インデントされた読みやすい形式で整形される
デバッグセッションと変数検査
- Pythonの
breakpoint()で対話型デバッガを起動した後、その場でオブジェクトを検査できる
- Pdbの例では、
import watまたはInsta-Loadスニペット貼り付けの後、wat / fooでローカル変数を検査し、cで実行を継続する
- ローカル変数とグローバル変数はそれぞれ
wat.locals、wat.globalsで確認できる
wat()を引数なしで呼ぶと、呼び出し元スタックのローカル変数をLocal variablesという見出しで出力する
Python内部の学習例
- Python内部の動作を理解する学習用途の例が含まれている
reversed([]) == reversed([])はFalseで、wat.s / reversed([])は値がlist_reverseiteratorオブジェクトで、型がlist_reverseiteratorであることを示す
wat / type('ObjectCreator', (), {})は、動的に作成したクラスの値、型type、signature: class ObjectCreator()を示す
wat / typeは、type自身の値、型type、class type(…)シグニチャ、type(object) -> the object's type、type(name, bases, dict, **kwds) -> a new typeというドキュメント、public属性mroなどを示す
wat.s / List[str]は、値typing.List[str]、型typing._GenericAlias、親型typing._BaseGenericAlias、typing._Final、シグニチャdef List(*args, **kwargs)を示す
wat(str | None)は、値str | None、型types.UnionTypeを示す
- Python組み込みオブジェクト探索の例として
wat / __builtins__、wat / ...が示されている
- WAT自体も検査できる
- 例として
wat.dunder / wat、wat.code / wat.__truediv__がある
内部動作の要約
inspect_format(obj, *, short=False, dunder=False, nodocs=False, long=False, code=False, caller=False, public=False, all=False)は、オブジェクト検査結果を文字列として構築する
all=Trueならdunder、long、code、callerがあわせて有効になる
public=Trueならprivate出力が無効になる
sys.stdout.isatty()が真なら、ターミナル幅を取得し、出力の上下に─区切り線を追加する
- 検査出力は、オブジェクト値、文字列表現、型、親型、長さ、シグニチャ、ドキュメント、ソースコード、属性セクションの順で生成される
- 属性検査は
dir(obj)を名前順に走査する
- dunder属性は
dunder設定がオフなら除外する
_で始まるprivate属性はprivate設定がオフなら除外する
getattr(obj, key)でBaseExceptionが発生した場合は、例外オブジェクトを値として使う
- callableオブジェクトは
inspect.signature(obj)をもとにシグニチャを整形する
- 失敗時は
(...)形式の代替シグニチャを返す
- クラスには
class 、coroutine functionにはasync def 、関数・メソッド・builtin・__name__を持つオブジェクトにはdef 接頭辞を付ける
code=Trueで、かつオブジェクトがクラスまたはcallableなら、inspect.getsource(obj)でソースコードを出力する
OSError、TypeError、IndentationError発生時は失敗メッセージを返す
- dictとlistのフォーマッタは、インデント深度が30を超えると
ERROR: too deeply nestedを返す
カラー出力とテーマ
- 環境変数でカラー出力を制御できる
WAT_COLOR="false"はコンソールのカラー出力を無効化する
WAT_COLOR="true"はnon-tty環境でもカラー出力を強制する
WAT_COLORS環境変数でカラーテーマをカスタマイズできる
- デフォルトテーマは
BAR=0;34,TRAIT=1;34,HEAD=1;37,STR=0;32,NUMBER=0;31,NONE=0;35,TRUE=1;32,FALSE=1;31,DOCS=2;37,KEYWORD=0;34,CALLABLE=1;32,VARIABLE=1;33,CODE=0;33形式のANSIカラーコード対応表
_strip_color(text)は、ANSI escape sequenceを正規表現で除去する
インスピレーション
1件のコメント
Hacker Newsのコメント
わあ、すごく良い。以前、似た用途で python-ls[0] を使っていたが、思い出せない理由で何かが壊れて、もうメンテナンスもされていない。
主に snoop[1] と pdbpp で構成しているデバッグ用ツールボックスに追加する予定。wat に望むのは、Jupyter でオブジェクト探索をもっと簡単にしてくれる ipy ウィジェットくらい。
base64 exec ハックも気に入った。Python を長く使ってきたのに、これまで考えたことも見たこともなかったので、今後いくつかの用途でぜひ使ってみるつもり。
[0] https://github.com/gabrielcnr/python-ls
[1] https://pypi.org/project/snoop/
面白そう。Python では dir をいつも使っていて、ドキュメントがいまいちな場合には公式ドキュメントより役に立つこともある。
対話型シェルは Python の本当の強みの一つなのに、その周辺にこういう新しいツールや革新がもっと多くないのは意外。
古くからある icecream の、より派手なバージョンのように見える。
https://github.com/gruns/icecream
知らないなら、下のほうにある他言語向け実装の一覧も見るといい。
https://github.com/gruns/icecream#icecream-in-other-language...
この種のツールは便利。
20年前には Zope 向けの オブジェクト・イントロスペクター を作っていた。
最近は devtools を毎日使い、icecream と q は時々使っている。wat も試してみる予定。
from wat import watプロジェクトの性格がこんなにクールなのに、同じ使用構文で単に
import watを提供していないのは意外。そうすれば好奇心旺盛なユーザーに wat/wat を試させて、トリックを発見させることもできたはずなのに。import watならよかったが、Python には モジュールを呼び出し可能にできない制約 がある。だから、より長いfrom wat import watになった。確かではないが、
import wat; wat.wat / objectのほうが便利かもしれない。とても便利そうではあるが、可読性を名目にまったく関係ない演算子、ここでは
/演算子 をオーバーロードする最近の流れに引っかかっているのは自分だけなのか気になる。/のオーバーロードは奇妙な選択だという点には同意する。それでもisをオーバーロードできない点 は惜しい。現実的にはwat(foo)だけでも十分だった気がする。面倒な import を避けるなら、
$PYTHONSTARTUPファイルに次を追加することもできる。try:from wat import watexcept ImportError:pass結局その出力を表示して、常に使えるように
PYTHONPATHが指すディレクトリに入れておいた。使い続けるかは様子見。
わあ、Python を学んでいたときにこういうツールがあったら 流れが変わっていた と思う。言語を学ぶとき、内部で何が起きているかを見ることが中心的な道筋なのに、Python の標準デバッグは控えめに言っても期待外れ。
代わりに pry を入れて熱心な Ruby ファンになったが、このツールなら Python をもう一度試してみる気にさせるかもしれない。
作者は機能を提供するため、内部的に標準ライブラリの Python inspect モジュール を使っている。もちろん、その上に多くの付加価値を加えている。
wat モジュールの
inspection.pyを見ればよい。2行目にこうある:
import inspect as std_inspect「手早く何かをデバッグしたいなら、同じセッションで何もインストールせずにこのインスペクターを使える」
「このスニペットを Python インタープリターに貼り付けて、その場でロードせよ」
プロジェクト README に プロジェクト全体のコピー を base64 エンコードした圧縮データとして入れておくという発想は、かなり巧妙。
特に、必要になる環境に事前に入れておくことを思いつかないかもしれない、こういうプロジェクトにはよく合っている。