はじめに
コードは書くより読む方が多いと言います。
私の仕事は全く別分野ですが、意思決定や設計判断を行う立場になってきていることもあり、圧倒的に手を動かして設計図を書くよりも、設計図書を読み返して理解することに時間を要します。
しかし、どうしても学習を進めるなかでコードを読むという優先度よりも、ハンズオンで書いてみるというのが優先度としては高くなる傾向にあるのが世の中です。
アウトプット優先であることが正義。自身が経験することで、理解をしていく。それも正しいと思います。
そんな中でも1度寄り道をして、コードを深層理解できるようなインプットをしてみました。
前提条件
- 学ぶことは好きだがキャパは小さい自分
- OSSの
Typerをただただ正座して読む - 軸は3軸(開発者、活用者、使用者)が存在すると想定
- 今回は活用者の目線で考えていく
- ChatGPTによる壁打ちをマップ化して深く読む
コードリーディング環境
コーディングエージェントの方が、全体像を読取りながら的確なアドバイスをしてくれそうです。
ただ、扱うまでの学習コストを最小限に抑える目的で、ChatGPTにプロジェクトを作成して、ライブラリのファイルを全ツッパしました。
カスタム指示として以下を記載。
あなたはtyperライブラリを使用するPythonエンジニアのプロフェッショナルです。
コードリーディングからコードの全てを理解するためのメンターとなって回答しください。
目的は、コードを読む能力、コードの設計を理解する能力、コードを自身で書くための具体的な思想と実装を身につけることが最重要事項です。
事実に基づいた回答を厳格に遵守して、予測や分からないことは回答しないでください。
全体像の把握をする
まずTyperは何をするものか
Typerは、ユーザーが使いやすく、開発者が作成を楽しめるCLI アプリケーションを構築するためのライブラリです。Python の型ヒントに基づいています。また、スクリプトを実行し、スクリプトを自動的に CLI アプリケーションに変換するコマンドライン ツールでもあります。
Typerによって、Clickの設定値を構築して渡すことで、Clickの実行エンジンによってCLIを実行します。
概要フローチャート
定義フェーズを実装する
READMEのサンプルコードを実装します。
Typerオブジェクトを作成して、関数をコマンドとして登録します。
import typer
app = typer.Typer()
@app.command()
def hello(name: str):
print(f"Hello {name}")
@app.command()
def goodbye(name: str, formal: bool = False):
if formal:
print(f"Goodbye Ms. {name}. Have a good day.")
else:
print(f"Bye {name}!")
if __name__ == "__main__":
app()
ターミナルで実行
❯ python3 sample.py hello EVERYONE
Hello EVERYONE
❯ python3 sample.py goodbye EVERYONE --formal
Goodbye Ms. EVERYONE. Have a good day.
❯ python3 sample.py hello --help
Usage: sample.py hello [OPTIONS] NAME
╭─ Arguments ─────────────────────────────────────────────────────────────╮
│ * name TEXT [required] │
╰─────────────────────────────────────────────────────────────────────────╯
╭─ Options ───────────────────────────────────────────────────────────────╮
│ --help Show this message and exit. │
╰─────────────────────────────────────────────────────────────────────────╯
非常に活用者の軸からするとシンプルです。
関数 → メインコマンド
引数 → 引数
Typerを読む
今回の軸として、活用者目線で読むということで、
使用されるTyperオブジェクトとデコレーターの@app.command()にフォーカスして正座して読んでいきます。
処理の構造ってどうなってんの
まずは一般的な処理って、各処理を渡す→処理される(呼出す)→受け取るというような制御部を持っているものなのか、それとも入力→加工→加工→出力というような段階的な構造なのかを聞きます。
プロンプトは以下
フローチャート単純化してください。
typerの構造は入力から枝分かれしていって複数の出力を持つ階層的な構造なのか、
それとも入力されたものを加工する処理に渡し、結果を次の処理へ渡して出力までするような、制御集約的な構造か。
回答は以下
結論(BLUF)
**Typer の処理フローは「階層ツリーを上から下へ辿る構造」**であり、
argv をキーに「どのノード(コマンド)を選ぶか」だけが制御の本質です。
以下に 処理フローを階層構造そのものとして 表します。
[ プロセス起動 ]
└─ argv(コマンドライン引数)
└─ Root Command(Typer / Click Group)
├─ global options の解釈
│
├─ Command A
│ ├─ options / arguments の解釈
│ │
│ ├─ Subcommand A1
│ │ ├─ options / arguments の解釈
│ │ └─ User Function A1
│ │ ├─ ユーザロジック実行
│ │ └─ stdout / stderr 出力
│ │
│ └─ Subcommand A2
│ ├─ options / arguments の解釈
│ └─ User Function A2
│ └─ 出力
│
└─ Command B
├─ options / arguments の解釈
└─ User Function B
└─ 出力
ユーザーからの入力後に、CLIを解析して階層的に入力内容に合うCommandを実行します。
先ほどの段落タイプに近い構造のようです。
ただ相違点としては、入力に対しての経路は常に1つしか存在しないという点が異なります。
確かに、加工処理がどこか異なれば、出力も全て異なるのは当たり前ではあります。
ただオプションという加工を共用で持つ場合は、このような思想に近くなりますが、出力が拡張されていくようになるのかと思います。
結果
結果として利用者側は User Function を登録するだけで、
Typer が内部で CLI 定義・引数解析・型変換を担い、
CLI アプリケーションとして実行可能な状態を構築してくれます。
次回へ
一般的な理解方針とTyperの全体像に触れました。
次回は、Typerの全体像の深掘りと、Typerの内部情報をどんどん深掘りしていきます。
有識者の方々の活発なアドバイスを是非ともお願いします!!
