argparseの勘所(Python2.7〜)

今までコマンドライン引数を処理するのにoptparseを使ってきたのですが、optparseは2.7以降は非推奨というか上位互換のargparseを使いましょうねというお達し(PEP389)が出ています。そんなわけで今書いているプログラムに関して移行作業を行いました。

argparseですが、基本的な使い方は、サンプルコードも多い充実したドキュメントがあるので、そちらを参照しましょう。この記事ではハマったポイントを紹介したいと思います。

コマンド不正の時に終了ではなく例外を投げる

argparseでは、指定されたコマンドが不正の場合、プログラムを終了してしまうArgumentParser.errorが呼ばれて、プログラムが落ちます。が、REPLで使い方を勉強するにも、ユニットテスト書くにも、exitされてしまうと困ります。正直これは設計ミスだと思います。

ということで対処法ですが、継承してオーバーライドしてしまえばOKだそうです。

class ArgumentParserError(Exception): pass

class ThrowingArgumentParser(argparse.ArgumentParser):
    def error(self, message):
        raise ArgumentParserError(message)
I want Python argparse to throw an exception rather than usage - Stack Overflow

フラグ+デフォルト値はconstで

基本的に、action="store"の場合、フラグを指定する時は値が必須となります。

>>> parser = argparse.ArgumentParser()
>>> parser.add_argument("-o", "--output")
_StoreAction(option_strings=['-o', '--output'], dest='output', nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None)
>>> parser.parse_args("-o hoge".split())
Namespace(output='hoge')
>>> parser.parse_args("-o".split())
usage: [-h] [-o OUTPUT]
: error: argument -o/--output: expected one argument

defaultを指定すればOKじゃないの?と思ってしまうのですが…

>>> parser = argparse.ArgumentParser()
>>> parser.add_argument("-o", "--output", default="hoge")
_StoreAction(option_strings=['-o', '--output'], dest='output', nargs=None, const=None, default='hoge', type=None, choices=None, help=None, metavar=None)
>>> parser.parse_args("-o".split())
usage: [-h] [-o OUTPUT]
: error: argument -o/--output: expected one argument

ダメでした。

というのも、defaultで与えられる値はフラグが指定されなかった時にデフォルトで生成されるものだからです。なのでparser.parse_args([])ならエラーにはなりません。

ではどうすれば良いのかというと、nargsとconstを使います。

>>> parser = argparse.ArgumentParser()
>>> parser.add_argument("-o", "--output", nargs="?", const="hoge", default="piyo")
_StoreAction(option_strings=['-o', '--output'], dest='output', nargs='?', const='hoge', default='piyo', type=None, choices=None, help=None, metavar=None)
>>> parser.parse_args([])
Namespace(output='piyo')
>>> parser.parse_args("-o".split())
Namespace(output='hoge')
>>> parser.parse_args("-o foo".split())
Namespace(output='foo')

フラグが指定されなければdefault値が、フラグだけ与えられた時はconstの値、フラグと値を指定された時はその値が入ります。

一応ドキュメントにも書いてありますが、これはハマりどころだと思います。