argparse の add_argument() には action パラメータがあり、store_true や count などの組み込みアクションが用意されている。しかし、引数を受け取ったときに独自の処理を挟みたい場合、組み込みアクションだけでは対応しきれないことがある。そんなとき、argparse.Action を継承したカスタムアクションを作ることで、引数の処理ロジックを自由に定義できる。
Action クラスの基本構造
カスタムアクションは argparse.Action を継承し、__call__ メソッドをオーバーライドして作る。
import argparse class UpperAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, values.upper()) parser = argparse.ArgumentParser() parser.add_argument("--name", action=UpperAction, help="名前(大文字に変換)") args = parser.parse_args(["--name", "alice"]) print(args.name) # ALICE
__call__ の引数はそれぞれ以下の役割を持つ。
| parser | ArgumentParser インスタンス |
| namespace | 解析結果を格納する Namespace オブジェクト |
| values | 引数に渡された値 |
| option_string | 使用されたオプション文字列(--name など) |
setattr(namespace, self.dest, ...) で解析結果に値をセットするのが基本パターンだ。self.dest には add_argument の dest に対応する属性名が入っている。
実用例:カンマ区切りをリストに変換
カンマ区切りの文字列を受け取ってリストに変換するカスタムアクションは、実務でもよく使われるパターンだ。
import argparse class CommaSplitAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): items = [v.strip() for v in values.split(",")] setattr(namespace, self.dest, items) parser = argparse.ArgumentParser() parser.add_argument("--tags", action=CommaSplitAction, help="タグ(カンマ区切り)") args = parser.parse_args(["--tags", "python, cli, argparse"]) print(args.tags) # ['python', 'cli', 'argparse']
nargs や type で対応できなくもないが、「カンマで区切って前後の空白を除去する」という一連の処理をアクションにまとめると、呼び出し側のコードがすっきりする。
実用例:値の範囲チェック
引数の値が特定の範囲に収まっているかをチェックし、範囲外ならエラーにするアクションも便利だ。
import argparse class RangeCheckAction(argparse.Action): def __init__(self, option_strings, dest, min_val=0, max_val=100, **kwargs): self.min_val = min_val self.max_val = max_val super().__init__(option_strings, dest, **kwargs) def __call__(self, parser, namespace, values, option_string=None): if not (self.min_val <= values <= self.max_val): parser.error( f"{option_string} の値は {self.min_val}〜{self.max_val} の範囲で指定してください" ) setattr(namespace, self.dest, values) parser = argparse.ArgumentParser() parser.add_argument( "--port", type=int, action=RangeCheckAction, min_val=1024, max_val=65535, help="ポート番号" ) args = parser.parse_args(["--port", "8080"]) print(args.port) # 8080
この例では __init__ もオーバーライドしている。min_val と max_val というカスタムパラメータを受け取るために、__init__ で独自の属性を設定してから super().__init__() を呼び出す必要がある。
parser.error() を呼ぶと、argparse 標準のエラーメッセージ形式で出力され、プログラムが終了コード 2で終了する。
Unix の慣習で、コマンドライン引数のエラーは終了コード 2 を返すのが一般的。
範囲外の値を指定した場合の出力は次のようになる。
$ python server.py --port 80 usage: server.py [-h] [--port PORT] server.py: error: --port の値は 1024〜65535 の範囲で指定してください
組み込みアクションとの比較
argparse には複数の組み込みアクションが用意されており、多くの場面ではこれらで十分に対応できる。
| アクション | 動作 | 用途 |
|---|---|---|
| store | 値を格納(デフォルト) | 一般的な引数 |
| store_true | True を格納 | フラグ |
| count | 出現回数をカウント | -v -vv のような冗長指定 |
カスタムアクションが必要になるのは、値の変換・検証・副作用を引数のパース時点で行いたい場合だ。type パラメータでも型変換は可能だが、エラーメッセージのカスタマイズや複数フィールドへの同時書き込みが必要なら、カスタムアクションのほうが柔軟に対応できる。
単純な型変換なら十分。type=int や type=float のほか、自作関数も渡せる
範囲チェック、複合的な変換、複数属性の同時更新、詳細なエラーメッセージが必要な場合に有効
カスタムアクションは argparse の拡張ポイントとして強力だが、あまり多用すると引数定義が複雑になる。まずは type や choices で対応できないか検討し、それでは不十分な場合にカスタムアクションを導入するのが望ましい。