nikkie-ftnextの日記

イベントレポートや読書メモを発信

argparseで作るサブコマンド、set_defaultsを使うとスッキリ書けます(激薄エントリポイント!)

はじめに

霞柱TUEEEEEEE!! nikkieです。

argparseでサブコマンドを実装する際の小ネタです。

目次

前回:argparseでサブコマンドを作る!

argparseはPython標準ライブラリの1つです。

先日、svn checkout, svn update, svn commitのようなサブコマンドを実装する例を取り上げました。

ドキュメントで見つけた、サブコマンドをスッキリ実装するtips

https://docs.python.org/ja/3/library/argparse.html#argparse.ArgumentParser.add_subparsers にある例を元にした紹介です1

サブコマンドを扱う1つの便利な方法は add_subparsers() メソッドと set_defaults() を組み合わせて、各サブパーサーにどの Python 関数を実行するかを教えることです。

ポイントはset_defaults
argsparse_argsの返り値のNamespaceオブジェクトを受け取る関数をサブコマンドの数だけ宣言します。
そしてその関数をset_defaultsサブコマンドのパーサに設定するのです!
これによりサブコマンドのパーサに結び付けられた、argsを引数とする関数が実行されます。
分岐が標準ライブラリ側にラップされている感覚ですね。

この記事のコードの動作環境は Python 3.10.9 です。

% python subcommand_tips.py -h
usage: subcommand_tips.py [-h] {foo,bar} ...

positional arguments:
  {foo,bar}

options:
  -h, --help  show this help message and exit

サブコマンド動作例です

% python subcommand_tips.py foo 1 -x 2
2.0
% python subcommand_tips.py bar XYZYX
((XYZYX))

過去の私はサブコマンド名を格納して、分岐を実装していました

set_defaultsを知った今なら言える、「その必要はないわ

サブコマンド名を格納して分岐を実装する例は以下です(ですがset_defaultsを使うのがオススメ!)
dest引数を使ってサブコマンド名を格納します2

dest - サブコマンド名を格納する属性の名前です。デフォルトは None で値は格納されません (add_subparsersのドキュメントより)

% python subcommand_before.py foo 1 -x 2
2.0
% python subcommand_before.py bar XYZYX
((XYZYX))

終わりに

argparseを使ったサブコマンド実装で、set_defaultsサブパーサに(Namespaceオブジェクトを引数とする)関数を設定するtipsを紹介しました。
サブコマンド名を格納して分岐を書いて、サブコマンドに応じて関数を呼ぶようなコードを書かなくてよくなります。

サブコマンドごとの処理の実行は、以下の1行だけになっちゃうんです。なんてスッキリ!

args.func(args)

私はPythonで実装するときに「エントリポイントを薄くする」3を信奉しています。
set_defaultsを使ったサブコマンド実装でエントリポイントが激薄になり、非常に好みだな〜と感じました。オススメです!


  1. ドキュメントではadd_subparsersrequired=Trueを指定していますが、必要性が腹落ちしなかったので削除しています
  2. https://docs.python.org/ja/3/library/argparse.html#dest
  3. rhoboroさんの「自信を持ってコードを書こう」より。https://docs.google.com/presentation/d/1qu3zFbzMh3AYhQ3DuDCDKbLlLqIrcklZdzi9fKyZPZQ/edit#slide=id.g6fea1dd43e_0_77if __name__ == "__main__":ブロック内もシンプルにする