Pythonistaなら知ってるオプションパーサ
2014-12-02
QiitaPythoncliCUIこの記事はPython Advent Calendar 2014 - Qiita 2 日目の記事です 前日は @kureikei さんのBlender 関連 でした
最近は golang でツールを作るのが流行っていますが、負けじと python ももっと盛り上がって欲しいですね ということで、コマンドラインツールを作る時に必要な引数・オプションパーサを紹介していきます
コンテンツ
Python からコマンドラインでの引数・オプションを処理します 使用するものは
sys.argv
argparser.ArgumentParser
docopt.docopt
の 3 種類
getopt
しか使えないような古い Python は切り捨てました
optparse
は deprecated になっているので、ここでは紹介しません
Deprecation of optparse
対応バージョン
argparse
が Python2.7、Python3.2 で追加されていて、
docopt
はdocopt/docoptによると
docopt is tested with Python 2.5, 2.6, 2.7, 3.2, 3.3 and PyPy.
とあります。 手元では 3.4 でも動きました コマンドラインツールとなるとどのバージョンを選ぶべきか難しいところですが、 2.7 系がまだ有力かもしれません(*要出典)が、今後のことを見据えて 3.4 系使っていきたいですね
題材
必須の引数 1 つ取り、それをそのままprint
する、というシンプルなコマンドラインツールを作ります
オプションとして
-h
:--help
でヘルプ-v
:--verbose
でうるさい出力にする-c <arg>
,--cat <arg>
: 引数と<arg>
を結合して出力する
という 3 つを実装しました
python hoge.py -h
# => ヘルプの表示
python hoge.py foo
# => input is foo
python hoge.py foo -v
# => your input is foo!!!
python hoge.py foo -c bar
# => concatenated: foobar
sys.argv を使う
一番基本的なやり方でしょう
引数やオプションはすべてsys.argv
の index で判断することとなります
今回は必須の引数があるため、sys.argv[1]
を決め打ちでその引数として扱えます
オプションは-
で始まるため、startswith('-')
で判断できます
import sys
def parser():
usage = 'Usage: python {} FILE [--verbose] [--cat <file>] [--help]'\
.format(__file__)
arguments = sys.argv
if len(arguments) == 1:
return usage
# ファイル自身を指す最初の引数を除去
arguments.pop(0)
# 引数として与えられたfile名
fname = arguments[0]
if fname.startswith('-'):
return usage
# - で始まるoption
options = [option for option in arguments if option.startswith('-')]
if '-h' in options or '--help' in options:
return usage
if '-v' in options or '--verbose' in options:
return 'your input is {}!!!'.format(fname)
if '-c' in options or '--cat' in options:
cat_position = arguments.index('-c') \
if '-c' in options else arguments.index('--cat')
another_file = arguments[cat_position + 1]
return 'concatnated: {}{}'.format(fname, another_file)
return 'input is {}'.format(fname)
if __name__ == '__main__':
result = parser()
print(result)
引数の順番が変わったりすると全く対応できなくなってしまうが、おぼえることが最小限で済むため、簡単に使える ほんのちょっとしたオプションを処理したい、とかの時はこれで十分だと思います
help はusage
で定義した文字列がそのまま表示されます
$ python sys_parser.py -h
# => Usage: python sys_parser.py FILE [--verbose] [--cat <file>] [--help]
argparse を使う
具体的にはargparse.ArgumentParser
です
add_argument
で色々と細かな設定が出来るようになっています
required=True
で必須項目としたり、dest
で変数の保存先を指定したり、真偽値を保存したり、変数の型を指定したり出来ます
from argparse import ArgumentParser
def parser():
usage = 'Usage: python {} FILE [--verbose] [--cat <file>] [--help]'\
.format(__file__)
argparser = ArgumentParser(usage=usage)
argparser.add_argument('fname', type=str,
help='echo fname')
argparser.add_argument('-v', '--verbose',
action='store_true',
help='show verbose message')
argparser.add_argument('-c', '--cat', type=str,
dest='another_file',
help='concatnate target file name')
args = argparser.parse_args()
if args.verbose:
return 'your input is {}!!!'.format(args.fname)
if args.another_file:
return 'concatenated: {}{}'.format(args.fname, args.another_file)
return 'input is {}'.format(args.fname)
if __name__ == '__main__':
result = parser()
print(result)
順番に関係なく引数を処理できるようになる点と、引数の型を指定できる点がメリットだと思います
help はhelp=...
で定義したものもいい感じに表示してくれます
$ python argument_parser.py -h
usage: Usage: python argument_parser.py FILE [--verbose] [--cat <file>] [--help]
positional arguments:
fname echo fname
optional arguments:
-h, --help show this help message and exit
-v, --verbose show verbose message
-c ANOTHER_FILE, --cat ANOTHER_FILE
concatnate target file name
サブコマンドを実装することも出来ます 16.4. argparse — コマンドラインオプション、引数、サブコマンドのパーサー — Python 3.4.2 ドキュメント
docopt を使う
docopt/docopt docstring に使い方を書けば、それを parse してインターフェイスを作ってくれます unix コマンドなどのドキュメントに慣れている人なら、抵抗なく受け入れられるはず
__doc__ = """{f}
Usage:
{f} <fname> [-v | --verbose] [-c | --cat <another_file>]
{f} -h | --help
Options:
-c --cat <ANOTHER_FILE> concatnate target file name
-v --verbose Show verbose message
-h --help Show this screen and exit.
""".format(f=__file__)
from docopt import docopt
def parse():
args = docopt(__doc__)
if args['--verbose']:
return 'your input is {}!!!'.format(args['<fname>'])
if args['--cat']:
return 'concatenated: {}{}'.format(args['<fname>'],
args['--cat'][0])
return 'input is {}'.format(args['<fname>'])
if __name__ == '__main__':
result = parse()
print(result)
ドキュメントが実装となるので、コードとドキュメントが同居する Python らしいといえます
慣れていないと docstring の書き方がやや難しく感じますが、非常に柔軟に引数を処理出来て、
また、git add
みたいなコマンドを作ることも簡単に出来る点もメリットで、
上の例だと<fname>
から<>
を除いてfname
にすれば、fname
というコマンドとなります
help は__doc__
に書いたものがそのまま表示されます
$ python docopt_parser.py -h
docopt_parser.py
Usage:
docopt_parser.py <fname> [-v | --verbose] [-c | --cat <another_file>]
docopt_parser.py -h | --help
Options:
-c --cat <ANOTHER_FILE> concatnate target file name
-v --verbose Show verbose message
-h --help Show this screen and exit.
所感
- 本当に単純なものなら
sys.argv
で十分っぽい - 少し複雑なら
docopt
使うと良さそう -args['<fname>']
とかするの気持ち悪ければargparse
もアリ - document の書き方で詰まったりするの嫌ならargparse
の方が簡単 Ansible
なんかではsys.argv
とargparse
とoptparse
の 3 つを使ってるようです - https://github.com/ansible/ansible/search?utf8=✓&q=optparse&type=Code - https://github.com/ansible/ansible/search?utf8=✓&q=argparse&type=Code - https://github.com/ansible/ansible/search?utf8=✓&q=optparse&type=Code
Python Advent Calendar 2014 - Qiita 明日は @akiniwa さんです
from: https://qiita.com/petitviolet/items/aad73a24f41315f78ee4