Pythonのargparseを使ってみた
Pythonでコマンドライノプションの解析をするには、次のような方法があります。
- sys.argvをまんま使ってゴリゴリ書く。
- getopt モジュール (gnuっぽい書き方ができる)
- optparse モジュール (割と新しい)
- argparse モジュール (もっと新しい)
argparseはPython2.7からだそうですが、他人に使ってもらうようなコマンドラインツールを書く機会がしばらくありませんでしたから、(optparseも)使ったことがありません。
今回、そういう機会があったので、argparseを使ってみました。
基本的な使い方は、次サイトの説明で学びました。
- http://d.hatena.ne.jp/rudi/20100805/1281020304
- http://docs.python.jp/2/library/argparse.html#metavar
- http://docs.python.org/2/howto/argparse.html
getoptと比べると次のような特徴があり、とってもお利口さんです。
- usageを勝手に作ってくれる。
- positional argument はどこにあってもいい
- パースした引数の値を、指定したnamespaceにセットしてくれる。
- 引数の型を指定できる(ファイルならオープンまでしてくれる)
ちなみに、今回作成したツールのargparseに関係する部分だけ、抜粋します。
parser = argparse.ArgumentParser( description="""ログ定義ファイルユーティリティ""") parser.add_argument('input', metavar='infile', type=argparse.FileType('r'), default=sys.stdin, help='入力ファイル(ログ定義ファイル)') parser.add_argument('-o', '--output', metavar='outfile', nargs='?', type=argparse.FileType('w'), default=sys.stdout, dest='output', help='出力ファイル(default:stdout)') parser.add_argument('-p', '--prefix', default='PREFIX', dest='prefix', help='enumプレフィクス(default:PREFIX)') parser.add_argument('-c', '--cpp', default='logdefine.h', dest='header', help='C++ヘッダファイル名(default:logdefine.h)') parser.add_argument('-v','--verbose', action='store_true', dest='verbose', help='冗長メッセージフラグ(default:False)') parser.add_argument('-d', '--debug', action='store_true', dest='debug', help='デバッグフラグ(default:False)') converter = Converter() args = parser.parse_args(namespace = converter) converter.convert()
とても使い勝手がよいのですが、default値の設定のところでハマりました。
各引数には、add_argumentでdefault値を指定できます。引数を省略した場合、parse_argsで指定したnamespaceにdefault値がセットされることになっています。
上のコードで、オプションを指定しないでparse_argsを実行したところ、
converter.header='logdefine.h' converter.prefix='PREFIX'
となってほしかったのですが、そうなりません。
よくよく調べてみると、次のように、converterのコンストラクタでその変数をセットしているからでした。
class Converter(object): def __init__(self): etc ・・・ self.header = '' self.prefix = '' ・・・ etc
初期値をセットするコードを取除くと、parse_argsでdefault値がセットさ
れるようになりました。
class Converter(object): def __init__(self): ・・・ #self.header = '' #self.prefix = '' ・・・
すでにnamespaceにメンバがある場合には設定しない、ということのようです。ドキュメントのどこかにしっかり書いてあるのかも知れませんが、斜め読みしただけでは分からなかったので、メモしときます。