🖥️

Pythonのargparseでオプション引数を動的に変える話

に公開

背景

argparse は簡単に実装できる,Python標準のコマンドライン引数の解析モジュールです。チュートリアルもあるので,細かい説明は省きます。
自分はここで色々なファイルを開けるツールを作っていますが,ファイルによって取りたいオプション引数は変わってきます。それをすべて普通に設定してしまうと特定のファイルでしか使われないオプションも表示されてしまい,ヘルプメッセージが煩雑になってしまいます。
ではどうするのか,を色々試行錯誤したので,その結果をまとめます。
ちなみに,この方法を使えば隠しオプションも実装できそうです。

argparseの挙動

現時点のargparseのチュートリアルより,argparseはこんな感じの使い方をします

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("echo")
args = parser.parse_args()
print(args.echo)
  1. ArgumentParser インスタンスを作成
  2. インスタンスにadd_argumentで引数を追加
  3. parse_argsで引数を解析

という流れだと理解しています。

実際にソースを読んでみると,

    def parse_args(self, args=None, namespace=None):
        args, argv = self.parse_known_args(args, namespace)
        if argv:
            msg = _('unrecognized arguments: %s') % ' '.join(argv)
            if self.exit_on_error:
                self.error(msg)
            else:
                raise ArgumentError(None, msg)
        return args

こうなっています。
parse_known_args は引数argsNoneの場合はsys.argv)を解析し,add_argsによって指定されたコマンドラインの位置引数,オプション引数はnamespaceの形でargsに,それ以外はargvとして返しているようです。そしてargvが空でなければ(つまり余計なコマンドラインの引数があれば)エラー終了する,という流れのようです。
ちなみに,-hを指定した場合はparse_known_argsの中でexit()するようです。

解決策

ということで,parse_known_argsを使えば,余計なコマンドライン引数を渡しても処理を継続してくれそうです。
最終的には,こんな感じで実装をしてみました(イメージ)。

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('file', help='file')
parser.add_argument('-t', '--type', help='specift the type',
                    choices=['A', 'B'])
tmpargs, rems = parser.parse_known_args()
if tmpargs.type == 'A':
    parser.add_argument('--a_option', action='store_true')
elif tmpargs.type == 'B':
    parser.add_argument('--b_option', nargs='*')
args = parser.parse_args()
main(args)

これで,コマンドラインで-t A を指定した場合は--a_optionが取れ,-t Bを指定した場合は--b_optionが取れます。

おまけ

隠しオプションの実装

上で書いたように,(ArgumentParseradd_help=Falseしない限り)コマンドラインで-hをした場合は,parse_known_argsの中で終了するようです。
そのため,

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--opt')
parser.parse_known_args()
parser.add_argument('--hidden_opt')
args = parser.parse_args()
main(args)

とすると,--hidden_optはヘルプには表示されないが取ることはできる,"隠しオプション"として機能します。

位置引数について

同様の手法でコマンドラインの位置引数を動的に変えられないかと試行錯誤しましたが,こちらは駄目なようでした。どうやらこのあたりを読むと,parse_known_argsの中で必須の引数が揃っているか確認し,足りないようならエラー終了してしまうようです。
(ただしsys.argvを使っていることは分かったので,自分はこれを使って対応しました。)

終わりに

以上です。argparseを使ってコマンドラインのオプション引数を動的に変えたいような人は少なそうですが,隠しオプション引数にも使えるので役に立てば幸いです。

Discussion