Help us understand the problem. What is going on with this article?

Pythonistaなら知ってるオプションパーサ

More than 1 year has passed since last update.

この記事はPython Advent Calendar 2014 - Qiita 2日目の記事です
前日は @kureikei さんのBlender関連 でした

最近はgolangでツールを作るのが流行っていますが、負けじとpythonももっと盛り上がって欲しいですね
ということで、コマンドラインツールを作る時に必要な引数・オプションパーサを紹介していきます

コンテンツ

Pythonからコマンドラインでの引数・オプションを処理します
使用するものは

  1. sys.argv
  2. argparser.ArgumentParser
  3. docopt.docopt

の3種類
getoptしか使えないような古いPythonは切り捨てました
optparseはdeprecatedになっているので、ここでは紹介しません
Deprecation of optparse

対応バージョン

argparseがPython2.7、Python3.2で追加されていて、
docoptdocopt/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('-')で判断できます

sys_parser.py
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で変数の保存先を指定したり、真偽値を保存したり、変数の型を指定したり出来ます

argparse_parser.py
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コマンドなどのドキュメントに慣れている人なら、抵抗なく受け入れられるはず

docopt_parser.py
__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.

所感

Python Advent Calendar 2014 - Qiita 明日は @akiniwa さんです

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away