Pythonのtyperライブラリで簡単にCLIを作る方法
生徒
「typerライブラリで簡単にCLIを作る方法を学びたいのですが、サンプルコードをどう読めばいいですか?」
先生
「まずは 引数でCSVファイルを指定するCLIツール という目的で考えると分かりやすいです。何を入力して、Pythonでどう処理し、何を結果として確認するかを分けて見ましょう。」
生徒
「SEO記事っぽい説明だけでなく、実際に動かすコードも見たいです。」
先生
「では、typerを中心に、実務でつまずきやすいエラー処理やログ出力も含めて確認していきましょう。」
1. 実行コマンド・引数・入力ファイルを決める
PythonのtyperライブラリでCLIを作る前に、実行コマンド、コマンドライン引数、入力ファイルの三つを整理します。今回は、利用者が実行時にCSVファイルのパスを指定し、そのファイルの内容をPythonで読み込むCLIツールを作ります。
コマンドライン引数とは、Pythonファイルを実行するときに追加で渡す値のことです。たとえば、python tool.py sales.csvと入力した場合、tool.pyが実行するプログラム、sales.csvが処理対象となる入力ファイルです。ファイル名をプログラム内へ固定せず、引数として受け取ることで、さまざまなCSVファイルに同じCLIツールを利用できます。
コマンドライン引数からファイル名を受け取る基本例
import sys
from pathlib import Path
if len(sys.argv) < 2:
print("使い方: python tool.py input.csv")
raise SystemExit(1)
input_file = Path(sys.argv[1])
print("入力ファイル:", input_file)
sys.argvには、コマンドラインから渡された値が順番に入ります。最初の値は実行したPythonファイル名で、二番目の値が利用者の指定したCSVファイル名です。そのため、引数が不足している場合は使い方を表示し、処理を終了しています。
この段階では、どのようなコマンドで実行し、何を入力として受け取り、どのファイルを処理するのかを理解できれば十分です。入力、処理、出力を先に決めておくと、次にtyperでコマンドライン引数を受け取る仕組みを作るときも、コードの役割を理解しやすくなります。
2. typerでコマンドライン引数を受け取る
typerは、Pythonの関数と型ヒントを使って、コマンドライン引数を受け取るCLIツールを簡単に作成できるライブラリです。最初にコマンドプロンプトやターミナルでpython -m pip install typerを実行し、typerをインストールしておきます。
次の例では、input_fileという引数でCSVファイル名を受け取ります。関数の引数にstrを指定することで、入力されたファイル名を文字列として扱えるようになります。
import typer
def main(input_file: str):
print("指定されたCSVファイル:", input_file)
if __name__ == "__main__":
typer.run(main)
このプログラムをtool.pyという名前で保存した場合は、python tool.py sales.csvのように実行します。実行すると、sales.csvがinput_fileへ渡され、指定されたCSVファイル名として画面に表示されます。
typerを使うと、引数を受け取るための複雑な解析処理を自分で書かなくても、関数の定義からCLIを作成できます。さらに、python tool.py --helpを実行すると、利用できる引数を確認するためのヘルプも自動的に表示されます。
3. ファイルパス・オプション・環境変数を検証する
CLIツールでは、入力されたファイルパスが存在するか、フォルダではなくファイルを指定しているかを処理前に確認することが大切です。typerのArgumentを使うと、CSVファイルの存在や読み取り可否を簡単に検証できます。
また、文字コードのように必要に応じて変更する値は、コマンドラインオプションや環境変数から受け取れるようにすると便利です。次の例では、CSVファイルのパスと文字コードを受け取ります。
from pathlib import Path
import typer
def main(
input_file: Path = typer.Argument(
...,
exists=True,
file_okay=True,
dir_okay=False,
readable=True,
),
encoding: str = typer.Option(
"utf-8",
"--encoding",
"-e",
envvar="CSV_ENCODING",
),
):
typer.echo(f"入力ファイル: {input_file}")
typer.echo(f"文字コード: {encoding}")
if __name__ == "__main__":
typer.run(main)
exists=Trueはファイルの存在を確認し、dir_okay=Falseはフォルダの指定を許可しない設定です。文字コードは--encodingまたは-eで指定でき、省略した場合はutf-8が使われます。環境変数CSV_ENCODINGから受け取ることもできるため、実行環境ごとに設定を変更できます。
4. 標準出力とloggingを使い分ける
PythonでCLIツールを作るときは、利用者に見せる標準出力と、処理状況を記録するログを使い分けます。CSVファイルの行数や完了メッセージなど、利用者が確認したい結果はtyper.echoで表示します。一方、処理の開始時刻や読み込んだファイルなど、あとから動作を調査するための情報はloggingで記録します。
import logging
import typer
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
)
logger = logging.getLogger(__name__)
def main(input_file: str):
logger.info("CSVファイルの読み込みを開始します")
logger.info("入力ファイル: %s", input_file)
typer.echo(f"{input_file}を受け取りました")
typer.echo("処理が完了しました")
if __name__ == "__main__":
typer.run(main)
typer.echoで表示した内容は、CLIツールを操作する利用者への案内になります。logger.infoは、プログラムがどこまで進んだかを確認するためのログ出力です。日時とログの重要度も表示されるため、処理が停止した場所や実行時の状況を調べやすくなります。
標準出力へ細かな処理状況をすべて表示すると、利用者が本当に必要な結果を見つけにくくなります。画面には実行結果を簡潔に表示し、詳しい処理状況はログへ記録するように分けると、見やすく保守しやすいPythonのCLIツールになります。
5. 終了コードで成功と失敗をシェルへ伝える
PythonのCLIツールでは、処理結果を画面に表示するだけでなく、終了コードを使って成功または失敗をシェルへ伝えます。一般的に、正常終了は0、入力ミスや処理エラーによる異常終了は1などのゼロ以外の数値で表します。
typerでは、typer.Exitを使って終了コードを指定できます。次の例では、入力ファイルの拡張子がCSVではない場合にエラーメッセージを表示し、終了コード1で処理を終了します。
import typer
def main(input_file: str):
if not input_file.lower().endswith(".csv"):
typer.echo(
"CSVファイルを指定してください。",
err=True,
)
raise typer.Exit(code=1)
typer.echo(f"{input_file}を受け取りました")
typer.echo("処理は正常に完了しました")
if __name__ == "__main__":
typer.run(main)
sample.csvを指定した場合は最後まで処理が進み、終了コード0で正常終了します。一方、sample.txtのようにCSV以外のファイルを指定すると、条件分岐の中でtyper.Exitが実行され、終了コード1がシェルへ返されます。
終了コードを設定しておくと、人が実行結果を確認するだけでなく、別のプログラムや定期実行の仕組みからCLIツールを呼び出した場合にも、処理が成功したか失敗したかを判定できます。エラーが発生したのに正常終了として扱われないよう、失敗時には適切な終了コードを返すことが大切です。
6. WindowsとLinuxで動かすときの注意点
PythonのCLIツールをWindowsとLinuxの両方で動かす場合は、実行コマンド、ファイルパス、文字コードの違いに注意します。Windowsではpython tool.py sales.csv、Linuxでは環境によってpython3 tool.py sales.csvのように実行します。
ファイルパスを文字列の結合で作ると、WindowsとLinuxで区切り文字が異なるため、正しく動かないことがあります。PythonのPathを使うと、実行環境に合った形式でファイルパスを扱えます。
from pathlib import Path
import typer
def main(input_file: Path):
absolute_path = input_file.resolve()
typer.echo(f"入力ファイル: {input_file}")
typer.echo(f"絶対パス: {absolute_path}")
typer.echo(f"ファイル名: {input_file.name}")
if __name__ == "__main__":
typer.run(main)
ファイルパスに空白が含まれる場合は、python tool.py "sales data.csv"のように引用符で囲んで指定します。また、Linuxではファイル名の大文字と小文字が区別されるため、Sales.csvとsales.csvは別のファイルとして扱われます。
日本語を含むCSVファイルでは、環境によって文字コードが異なることもあります。WindowsとLinuxで同じCLIツールを使うときは、実際に利用するPythonのバージョン、typerのインストール状況、入力ファイルのパスと文字コードをそれぞれ確認しておくと安心です。
7. CLIツールとして使える完成コード
ここまで確認したコマンドライン引数、ファイルパス、オプション、ログ出力、終了コードを組み合わせて、CSVファイルの行数と列名を確認できるCLIツールを作ります。次のコードをcsv_tool.pyという名前で保存してください。
import csv
import logging
from pathlib import Path
import typer
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
)
logger = logging.getLogger(__name__)
def main(
input_file: Path = typer.Argument(
...,
exists=True,
file_okay=True,
dir_okay=False,
readable=True,
help="読み込むCSVファイルのパス",
),
encoding: str = typer.Option(
"utf-8",
"--encoding",
"-e",
help="CSVファイルの文字コード",
),
):
logger.info("CSVファイルの読み込みを開始します")
logger.info("入力ファイル: %s", input_file)
try:
with input_file.open(
mode="r",
encoding=encoding,
newline="",
) as file:
reader = csv.reader(file)
header = next(reader, None)
if header is None:
typer.echo(
"CSVファイルにデータがありません。",
err=True,
)
raise typer.Exit(code=1)
row_count = sum(1 for row in reader)
except UnicodeDecodeError:
typer.echo(
"文字コードが一致していません。",
err=True,
)
raise typer.Exit(code=1)
except OSError as error:
typer.echo(
f"ファイルを読み込めませんでした: {error}",
err=True,
)
raise typer.Exit(code=1)
typer.echo(f"ファイル名: {input_file.name}")
typer.echo(f"列名: {', '.join(header)}")
typer.echo(f"データ行数: {row_count}")
typer.echo("CSVファイルの確認が完了しました")
logger.info("CSVファイルの読み込みが完了しました")
if __name__ == "__main__":
typer.run(main)
実行するときは、コマンドプロンプトやターミナルでpython csv_tool.py sales.csvと入力します。文字コードを変更する場合は、python csv_tool.py sales.csv --encoding shift_jisのようにオプションを追加します。
指定したCSVファイルが存在すれば、ファイル名、列名、データ行数が標準出力へ表示されます。ファイルが空の場合や文字コードが一致しない場合は、原因を示すメッセージを表示し、終了コード1で処理を終了します。正常に読み込めた場合は終了コード0となるため、手動実行だけでなく、ほかのプログラムから呼び出すCLIツールとしても利用できます。
8. 定期実行やCIへつなげる改善ポイント
typerで作成したPythonのCLIツールは、手動で実行するだけでなく、決まった日時に自動実行する処理にも利用できます。Windowsではタスクスケジューラ、Linuxではcronへ実行コマンドを登録すると、CSVファイルの確認や集計を毎日または毎週、自動的に実行できます。
定期実行へつなげる場合は、処理結果をログへ残し、入力ファイルや出力先を引数やオプションで変更できるようにしておくことが大切です。また、正常終了と異常終了を終了コードで区別すると、失敗した処理を見つけやすくなります。
GitHub ActionsなどのCIでPythonのCLIツールを実行すれば、コードを更新したときに自動テストを行い、引数の受け取りやCSVファイルの読み込みが正しく動くかを確認できます。pytestを使って正常な入力、存在しないファイル、空のCSVファイルなどをテストすると、機能を追加したときの不具合も早く発見できます。
最初から多くの自動化機能を追加する必要はありません。まずは手動実行で動作を確認し、次にログ出力、設定ファイル、pytestによるテスト、定期実行、CIの順に広げると、初心者でも保守しやすいPythonのCLIツールへ段階的に改善できます。
まとめ
Pythonのtyperライブラリを利用すると、コマンドラインから実行できるCLIツールを、少ない記述で分かりやすく作成できます。CLIとは、画面上のボタンを操作する代わりに、ターミナルやコマンドプロンプトへコマンドを入力して利用する仕組みです。ファイルの変換、データの集計、定期処理、開発作業の自動化など、同じ作業を何度も実行するときに役立ちます。
Pythonでコマンドライン引数を扱う方法には、標準機能を使って引数の個数や位置を調べる方法もあります。しかし、引数やオプションが増えると、入力値の取得、型変換、必須チェック、説明文の表示などを自分で管理しなければなりません。typerを使えば、関数の引数と型ヒントをもとに、コマンドライン引数の受け取りや入力値の検証を分かりやすく構成できます。
typerでCLIを作成する基本の流れ
typerを使ったPythonのCLI開発では、最初にツールの目的を明確にします。今回のようにCSVファイルを読み込むツールであれば、入力するファイルパス、実行する処理、画面へ表示する結果、異常が起きた場合の動きを整理します。処理の内容を先に分けておくと、必要な引数やオプションを判断しやすくなります。
基本的な作成手順は、typerを読み込み、アプリケーションを作成し、コマンドとして実行する関数を定義するという流れです。関数の引数には、ファイルパスや文字列、数値などの型を指定できます。型を明確にすることで、利用者が誤った値を入力したときに問題を見つけやすくなり、プログラムを保守する人にも引数の意味が伝わります。
引数とオプションの違いを理解する
CLIツールでは、処理に必ず必要となる値を引数として受け取り、必要に応じて動作を変更する値をオプションとして受け取る設計が一般的です。CSVファイルを読み込むツールなら、処理対象となるファイルパスは引数にできます。一方、文字コード、出力先、表示する行数、詳細なログを表示するかどうかなどは、オプションとして用意すると使いやすくなります。
引数とオプションを適切に分けると、実行コマンドを見ただけで何を処理するツールなのかが分かります。すべての設定を必須引数にすると毎回の入力が長くなり、反対に必要な値までオプションにすると入力忘れが起きやすくなります。利用者が何度も実行する場面を想像しながら、必須にする値と初期値を用意する値を整理することが大切です。
CSVファイルを受け取るCLIのサンプルプログラム
次のサンプルでは、コマンドライン引数からCSVファイルのパスを受け取り、Pandasでデータを読み込みます。ファイルが存在することを確認してから処理し、読み込んだ行数、列数、列名を表示します。文字コードはオプションとして変更できるため、日本語を含むCSVファイルにも応用できます。
from pathlib import Path
import logging
import pandas as pd
import typer
app = typer.Typer(
help="CSVファイルの内容を確認するCLIツールです。"
)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
)
logger = logging.getLogger(__name__)
@app.command()
def inspect(
input_file: Path = typer.Argument(
...,
help="読み込むCSVファイルのパス",
exists=True,
file_okay=True,
dir_okay=False,
readable=True,
),
encoding: str = typer.Option(
"utf-8",
"--encoding",
"-e",
help="CSVファイルの文字コード",
),
limit: int = typer.Option(
5,
"--limit",
"-n",
min=1,
help="画面に表示する行数",
),
) -> None:
logger.info("CSVファイルの読み込みを開始します")
try:
data = pd.read_csv(input_file, encoding=encoding)
except UnicodeDecodeError:
typer.secho(
"文字コードが一致していません。",
fg=typer.colors.RED,
err=True,
)
raise typer.Exit(code=1)
except pd.errors.EmptyDataError:
typer.secho(
"CSVファイルにデータがありません。",
fg=typer.colors.RED,
err=True,
)
raise typer.Exit(code=1)
typer.echo(f"ファイル名: {input_file.name}")
typer.echo(f"行数: {len(data)}")
typer.echo(f"列数: {len(data.columns)}")
typer.echo(f"列名: {', '.join(data.columns)}")
typer.echo(data.head(limit).to_string(index=False))
logger.info("CSVファイルの確認が完了しました")
if __name__ == "__main__":
app()
このサンプルプログラムでは、ファイルパスにPathを指定し、存在するファイルだけを受け付けるようにしています。存在しないファイルやフォルダが入力された場合は、実際の読み込み処理へ進む前に入力内容を確認できます。ファイルの存在確認を処理の途中で行うのではなく、コマンドライン引数を受け取る段階で検証できることは、typerを使用する利点の一つです。
limitオプションには、表示する行数として一以上の数値を受け取る条件を設定しています。利用者が不適切な数値を指定した場合に、そのまま処理を続けない設計にすることで、予想外の結果を防げます。Pythonの型ヒントとtyperの入力検証を組み合わせると、CLIツールの安全性と読みやすさを高められます。
エラー処理と終了コードを整える
実務で利用するCLIツールでは、正常に動く場合だけでなく、ファイルが存在しない場合、CSVファイルが空の場合、文字コードが一致しない場合、列名が不足している場合なども考える必要があります。問題が起きたときに長いエラー情報だけを表示すると、利用者は何を修正すればよいのか判断できません。
typerの画面出力を使って簡潔なエラーメッセージを表示し、失敗を表す終了コードをシェルへ返すと、原因を判断しやすいCLIになります。終了コードは、人が画面を見て実行するときだけでなく、定期実行や自動処理からCLIツールを呼び出すときにも重要です。成功した場合と失敗した場合を機械的に判定できれば、後続処理を続けるか停止するかを制御できます。
標準出力とログ出力を使い分ける
CLIツールの標準出力には、利用者が確認したい処理結果を表示します。CSVファイルの行数、列数、列名、保存先などが代表例です。一方、ログには、処理を開始した時刻、読み込んだ対象、処理が成功したか、どこで失敗したかなど、あとから動作を調査するための情報を記録します。
すべての情報を標準出力へ表示すると重要な結果を見つけにくくなり、すべてをログへ記録すると通常の利用者に必要な情報が伝わりません。実行した人がすぐに必要とする内容と、運用担当者が問題調査で必要とする内容を分けることが、分かりやすいPython CLIツールを作るポイントです。
WindowsとLinuxで動かすための確認項目
PythonのCLIツールをWindowsとLinuxの両方で利用する場合は、ファイルパスの区切り方、文字コード、改行コード、実行コマンドの違いに注意します。文字列をつなげてファイルパスを作るよりも、Pathを利用したほうが、異なる環境で扱いやすいコードになります。
開発したパソコンで動作しただけで完成と判断せず、実際に利用する環境でも確認してください。Pythonやtyper、Pandasなどのバージョンが異なると、同じプログラムでも動作が変わる場合があります。利用したライブラリとバージョンを記録しておくと、別のパソコンへ移行するときや、時間が経ってから再実行するときに環境を再現しやすくなります。
保守しやすいCLIツールへ発展させる方法
小さなCLIツールでは、一つの関数にすべての処理を書いても動かせます。しかし、機能が増えてくると、引数を受け取る処理、入力値を検証する処理、CSVファイルを読み込む処理、データを集計する処理、結果を保存する処理を分けたほうが修正しやすくなります。
関数名や変数名には、その処理の目的が分かる名前を付けます。さらに、入力値と期待する結果を決めてテストを作成すると、機能追加によって以前の処理が壊れていないかを確認できます。最初はファイル内容を表示するだけのCLIでも、CSVの集計、条件による抽出、別ファイルへの保存、処理結果の通知などへ段階的に発展させられます。
定期実行や継続的インテグレーションへ組み込む場合は、対話による入力を必要としない構成にすることも重要です。引数、オプション、環境変数、設定ファイルを使って実行条件を渡せるようにすると、人が操作しなくても同じ処理を繰り返せます。実行結果をログへ残し、失敗時に適切な終了コードを返す設計も、自動化しやすいCLIツールにつながります。
typerを学習するときの復習ポイント
- CLIツールの入力、処理、出力を分けて考える
- 必須の値は引数、変更可能な設定はオプションとして整理する
- 型ヒントを使って受け取る値の種類を明確にする
- ファイルの存在、文字コード、入力値の範囲を処理前に検証する
- 利用者向けの標準出力と調査用のログ出力を使い分ける
- 成功と失敗を終了コードによって呼び出し元へ伝える
- 読み込み、検証、処理、保存を小さな関数へ分割する
- WindowsとLinuxの実行環境の違いを確認する
Pythonのtyperライブラリの使い方を身につけるには、完成したコードをそのまま写すだけでなく、引数名、オプションの初期値、表示する行数、CSVファイルの文字コードなどを変更して試すことが大切です。存在しないファイルや空のファイルも指定し、どのようなエラーが表示されるかを確認すると、正常時と異常時の両方を理解できます。
typerによるCLI開発の基本を理解すれば、日常的なファイル操作やデータ処理をコマンド一つで実行できるようになります。小さな作業の自動化から始め、ログ出力、例外処理、テスト、定期実行へ少しずつ広げることが、実務で利用できるPythonコマンドラインツールを作る近道です。
生徒
「typerは、コマンドライン引数を受け取るためだけのライブラリだと思っていましたが、入力値の型やファイルの存在も確認できるのですね。」
先生
「そのとおりです。関数の引数と型ヒントをもとにCLIを組み立てられるので、引数の解析や入力チェックを分かりやすく記述できます。」
生徒
「必ず必要なCSVファイルは引数にして、文字コードや表示行数はオプションにすると、実行コマンドも使いやすくなると理解しました。」
先生
「入力の役割を整理できています。利用者が毎回指定しなければならない値と、必要なときだけ変更する値を分けることが、扱いやすいCLIツールの設計につながります。」
生徒
「ファイルがない場合や文字コードが違う場合も考えて、エラーメッセージと終了コードを用意する必要があるのですね。」
先生
「正常に処理できることだけでなく、失敗した理由を利用者へ伝えられることも大切です。終了コードを正しく返せば、定期実行や自動処理から呼び出した場合にも成功と失敗を判定できます。」
生徒
「次はCSVの内容を表示するだけでなく、カテゴリ別に集計して、結果を別のファイルへ保存するCLIへ変更してみます。」
先生
「読み込み、検証、集計、保存を別々の関数へ分けながら作ってみましょう。処理を小さく分ければテストしやすくなり、あとからオプションや新しいコマンドを追加するときにも修正しやすくなります。」