※ 追記あり
argparse モジュールを使っているうち、気になった、というか気に入らないことがあったのでメモしておく。
とりあえず、こんなスクリプトがあったとする。
import argparse parser = argparse.ArgumentParser(description='Hello world program.') parser.add_argument('name', metavar='NAME', action='store', help='specify name instead "world".') parser.add_argument('-m', '--morning', dest='greeting', action='store_const', const='Good morning', default='Hello', help='good morning') parser.add_argument('-t', '--times', dest='times', type=int, action='store', default=1, metavar='N', help='repeat N times') args = parser.parse_args() for n in range(args.times): print '%s, %s.' % (args.greeting, args.name)
これは以前 argparse を試してみたときのスクリプトにちょっと変更を加えたもので、どこを変えたかというと、位置引数 NAME のデフォルト値をなくして必須の引数にした点だ。
^o^ > python hello2.py -h usage: hello2.py [-h] [-m] [-t N] NAME Hello world program. positional arguments: NAME specify name instead "world". optional arguments: -h, --help show this help message and exit -m, --morning good morning -t N, --times N repeat N times ^o^ > python hello2.py Andy Hello, Andy. ^o^ > python hello2.py -m Andy Good morning, Andy. ^o^ > python hello2.py -m -t 3 Andy Good morning, Andy. Good morning, Andy. Good morning, Andy. ^o^ > python hello2.py usage: hello2.py [-h] [-m] [-t N] NAME hello2.py: error: too few arguments
最後の実行例のように、NAME 引数を省略するとエラーになる。これは期待通りの動作。
で、もうひとつ覚えておいてほしいのは一番最初の -h オプションを指定した実行例。NAME 引数が無いにもかかわらずエラーにならずにヘルプが表示されている。これも期待通りの動作だ。
さて、本題はこれから。一通りの機能を実装し終わったら(そしてこれからも機能を拡張するつもりがあるなら)、スクリプトのバージョンを表示するオプションを追加したくなる。
というわけで、バージョンを表示する -v (–version) オプションを追加したのがこれだ。
import argparse script_version = "v0.1.0" parser = argparse.ArgumentParser(description='Hello world program.') parser.add_argument('name', metavar='NAME', action='store', help='specify name instead "world".') parser.add_argument('-v', '--version', dest='version', action='store_true', help='show version and exit') parser.add_argument('-m', '--morning', dest='greeting', action='store_const', const='Good morning', default='Hello', help='good morning') parser.add_argument('-t', '--times', dest='times', type=int, action='store', default=1, metavar='N', help='repeat N times') args = parser.parse_args() if args.version: print script_version exit() for n in range(args.times): print '%s, %s.' % (args.greeting, args.name)
ところが、これは期待通りには動作しない。
^o^ > python hello2a.py -v usage: hello2a.py [-h] [-v] [-m] [-t N] NAME hello2a.py: error: too few arguments
NAME 引数が無いために、引数が足りないと怒られてしまう。
この問題は、NAME 引数を定義するところで、nargs=’?’ とすることでとりあえず回避できる。
import argparse script_version = "v0.1.0" parser = argparse.ArgumentParser(description='Hello world program.') parser.add_argument('name', metavar='NAME', nargs='?', action='store', help='specify name instead "world".') parser.add_argument('-v', '--version', dest='version', action='store_true', help='show version and exit') parser.add_argument('-m', '--morning', dest='greeting', action='store_const', const='Good morning', default='Hello', help='good morning') parser.add_argument('-t', '--times', dest='times', type=int, action='store', default=1, metavar='N', help='repeat N times') args = parser.parse_args() if args.version: print script_version exit() for n in range(args.times): print '%s, %s.' % (args.greeting, args.name)
^o^ > python hello2b.py -v v0.1.0
が、一方でこれは別の問題を引き起こす。NAME 引数が無くてもスクリプトが動いてしまうのだ。これは期待している動作とは違う。
^o^ > python hello2b.py Hello, None.
NAME 引数がない場合はちゃんとエラーになってほしい。でもって -v オプションを指定したときには NAME 引数が無くてもエラーになってほしくない。思い出してほしいが -h オプションはまさにそのように動作している。どうにかして期待通りに動作するようにはできないものだろうか。argparse のドキュメントを見たもののどうも解決策はなさそうに見える。
cf. http://docs.python.jp/2/library/argparse.html
で、結局、NAME 引数が指定されているかどうかを自前でチェックするようにした。
import argparse script_version = "v0.1.0" parser = argparse.ArgumentParser(description='Hello world program.') parser.add_argument('name', metavar='NAME', nargs='?', action='store', help='specify name instead "world".') parser.add_argument('-v', '--version', dest='version', action='store_true', help='show version and exit') parser.add_argument('-m', '--morning', dest='greeting', action='store_const', const='Good morning', default='Hello', help='good morning') parser.add_argument('-t', '--times', dest='times', type=int, action='store', default=1, metavar='N', help='repeat N times') args = parser.parse_args() if args.version: print script_version exit() if args.name is None: print parser.prog + ": error: too few arguments. type " + parser.prog + " -h for help" exit() for n in range(args.times): print '%s, %s.' % (args.greeting, args.name)
これで期待通り。
^o^ > python hello2c.py -v v0.1.0 ^o^ > python hello2c.py hello2c.py: error: too few arguments. type hello2c.py -h for help
でもやっぱり、argparse で何とかできてほしいなあ。
[追記]
argparse のドキュメントをよくよく読んでみたら、やっぱり解決策があった。
add_argument() の引数に、action=”version” と version=バージョンナンバー を指定すればいい。直したのがこれ:
import argparse script_version = "v0.1.1" parser = argparse.ArgumentParser(description='Hello world program.') parser.add_argument('name', metavar='NAME', action='store', help='specify name instead "world".') parser.add_argument('-v', '--version', action='version', version=script_version, help='show version and exit') parser.add_argument('-m', '--morning', dest='greeting', action='store_const', const='Good morning', default='Hello', help='good morning') parser.add_argument('-t', '--times', dest='times', type=int, action='store', default=1, metavar='N', help='repeat N times') args = parser.parse_args() for n in range(args.times): print '%s, %s.' % (args.greeting, args.name)
実行結果:
^o^ > python hello2d.py -h usage: hello2d.py [-h] [-v] [-m] [-t N] NAME Hello world program. positional arguments: NAME specify name instead "world". optional arguments: -h, --help show this help message and exit -v, --version show version and exit -m, --morning good morning -t N, --times N repeat N times ^o^ > python hello2d.py -v v0.1.1 ^o^ > python hello2d.py Andy Hello, Andy. ^o^ > python hello2d.py usage: hello2d.py [-h] [-v] [-m] [-t N] NAME hello2d.py: error: too few arguments