2 ポイント 投稿者 GN⁺ 2024-07-26 | 1件のコメント | WhatsAppで共有
  • WATは、Pythonランタイム上で正体不明のオブジェクトを素早く把握するためのインスペクタで、型・値・属性・メソッド・親型・シグネチャ・ドキュメント・ソースコードまで一度に確認できる
  • 基本的な使い方はwat / objectで、wat(object)と同じように動作し、wat.short / 'foo''foo' | wat.shortwat('foo', short=True)のような複数の構文をサポートする
  • .short.dunder.long.code.caller.public.all.ret.strなどのmodifierをチェーンして、出力範囲、返却方法、カラー出力、呼び出し位置表示を調整できる
  • インストールはpip install watの後にimport watで行え、素早いデバッグのために Insta-Load スニペットをPythonセッションに貼り付ければ、同じセッション内でインストールなしでも使える
  • Django Userre.matchpathlibcolorsys.hsv_to_rgbtyping.List[str]str | None などの例は、WATがデバッグ・REPL探索・Python内部の学習に使えることを示している

WATがすること

  • WATは、Pythonオブジェクトをランタイムで探索・検査するためのツール
  • 正体不明のオブジェクトが何なのか把握しにくいとき、Pythonコンソールでwatインスペクタを使ってオブジェクトの正体を調べられる
  • 任意のobjectに対してwat / objectを実行すると、次の情報を確認できる
    • オブジェクトの type
    • 整形された値
    • 変数とメソッド
    • 親型
    • シグニチャ
    • ドキュメント
    • ソースコード
  • 同じ詳細検査はwat(object)構文でも利用できる
  • Watは英語のwhatの変形で、混乱や不快感を表すときに使われる言葉として紹介されている

基本的な使い方と構文

  • 素早い入力のために除算演算子を使う
    • wat / foowat(foo)と同じ
  • 同じ検査に対して複数の構文を使える
    • 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はbase64zlibを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 / userstr: adminrepr: <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.localswat.globalsで確認できる
  • wat()を引数なしで呼ぶと、呼び出し元スタックのローカル変数をLocal variablesという見出しで出力する

Python内部の学習例

  • Python内部の動作を理解する学習用途の例が含まれている
  • reversed([]) == reversed([])Falseで、wat.s / reversed([])は値がlist_reverseiteratorオブジェクトで、型がlist_reverseiteratorであることを示す
  • wat / type('ObjectCreator', (), {})は、動的に作成したクラスの値、型typesignature: class ObjectCreator()を示す
  • wat / typeは、type自身の値、型typeclass type(…)シグニチャ、type(object) -> the object's typetype(name, bases, dict, **kwds) -> a new typeというドキュメント、public属性mroなどを示す
  • wat.s / List[str]は、値typing.List[str]、型typing._GenericAlias、親型typing._BaseGenericAliastyping._Final、シグニチャdef List(*args, **kwargs)を示す
  • wat(str | None)は、値str | None、型types.UnionTypeを示す
  • Python組み込みオブジェクト探索の例としてwat / __builtins__wat / ...が示されている
  • WAT自体も検査できる
    • 例としてwat.dunder / watwat.code / wat.__truediv__がある

内部動作の要約

  • inspect_format(obj, *, short=False, dunder=False, nodocs=False, long=False, code=False, caller=False, public=False, all=False)は、オブジェクト検査結果を文字列として構築する
    • all=Trueならdunderlongcodecallerがあわせて有効になる
    • 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)でソースコードを出力する
    • OSErrorTypeErrorIndentationError発生時は失敗メッセージを返す
  • 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件のコメント

 
GN⁺ 2024-07-26
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 の本当の強みの一つなのに、その周辺にこういう新しいツールや革新がもっと多くないのは意外。

    • help() 関数もある。本当に便利。
  • 古くからある 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 wat
    except ImportError:
    pass

    • さらに、かなりクールな base64 インラインインポーター を追加することもできる。
      結局その出力を表示して、常に使えるように PYTHONPATH が指すディレクトリに入れておいた。
      使い続けるかは様子見。
  • わあ、Python を学んでいたときにこういうツールがあったら 流れが変わっていた と思う。言語を学ぶとき、内部で何が起きているかを見ることが中心的な道筋なのに、Python の標準デバッグは控えめに言っても期待外れ。
    代わりに pry を入れて熱心な Ruby ファンになったが、このツールなら Python をもう一度試してみる気にさせるかもしれない。

  • 作者は機能を提供するため、内部的に標準ライブラリの Python inspect モジュール を使っている。もちろん、その上に多くの付加価値を加えている。
    wat モジュールの inspection.py を見ればよい。
    2行目にこうある:
    import inspect as std_inspect

  • 「手早く何かをデバッグしたいなら、同じセッションで何もインストールせずにこのインスペクターを使える」
    「このスニペットを Python インタープリターに貼り付けて、その場でロードせよ」
    プロジェクト README に プロジェクト全体のコピー を base64 エンコードした圧縮データとして入れておくという発想は、かなり巧妙。
    特に、必要になる環境に事前に入れておくことを思いつかないかもしれない、こういうプロジェクトにはよく合っている。