※ 追記あり
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