Pythonには標準でコマンドラインオプションを解析するモジュールがついてるんですが、
愚直に書いてるとすごくだらだらと長くなってしまうし、ぱっと見わかりづらいですよね。
それにPython2.7以上とかだったりで、結局古い方のモジュール使わないといけないし・・・とか。
そこで簡単にオプションをパースできるdocoptとschemaについて紹介します。
docopt
docopt - Pythonic argument parser, that will make you smile
docoptはPython2.5から3.3まで対応してます。
普通のパーサーはオプションのプログラムを書いて、そこからヘルプの文字列を生成したりするのですが、 docoptの場合はdocstringからパーサーを生成するという実にpythonicなライブラリです。
使い方は説明するより実際に試してみたほうが早いと思います。 以下のサイトで実際にdocstringを書いて実行することができます。
パースされるとDictionaryとして返ってくるのであとはそれを参照するだけです。
example
実際にdocoptを使用したツールを作ってみました。
ファイル(複数可)または標準入力から文字列を受け取り、ランダムに行を選択し出力するだけのツールです。
コマンドラインオプションのdocstringはrandfilter.pyの24行目からです。
"""
randfilter
Usage:
randfilter (-n <num> | -p <probability>) [-u | --unorder] [-i | --ignore-empty] [<files>...]
randfilter -h | --help
randfilter -v | --version
Options:
<files>... Choose and output lines at random in files. If omitted, use stdin.
-n <num> The choise number of lines.
-p <probability> The choise probability of lines. The value is 0.0 to 1.0.
-u --unorder Output lines are unordered.
-i --ignore-empty Ignore empty line.
-h --help Show this screen.
-v --version Show version.
"""
(これをtry.docopt.orgにコピペして試すこともできます)
あとはmain関数で
from docopt import docopt
args = docopt(__doc__, version="{0} {1}".format(NAME, VERSION))
としているだけです。これだけでパースできます。
version
引数はrandfilter --version
と打った時に表示する文字列を指定しているだけなので省略可です。
python randfilter.py -n 5 --unorder --ignore-empty README.rst setup.py
というオプションで実行すると以下の様なDictionaryになって返ってきます。
{'--help': False,
'--ignore-empty': True,
'--unorder': True,
'--version': False,
'-n': '5',
'-p': None,
'<files>': ['README.rst', 'setup.py']}
すごく簡単ですね。
しかし、docoptはオプションに与えられた値の検証までは行いません。そこでschemaです。
schema
schema - Simple data validation library
schemaは名前の通り、Pythonのデータ構造のスキーマを検証するライブラリです。 Python2.6から3.3まで対応しています。
詳しい説明は上記のページに載っているので、実際に使用した例を載せます。
example
これも先ほどのrandfilterで実際に使用した例です。
randfilter.pyのvalidate_args関数内のコードです。
schema = Schema({
'-n': Or(None, And(Use(int), lambda n: 0 <= n), error="-n should be positive integer"),
'-p': Or(None, And(Use(float), lambda n: 0.0 <= n <= 1.0), error="-p should be float 0 <= N <= 1.0"),
'--unorder': bool,
'--ignore-empty': bool,
'--help': bool,
'--version': bool,
'<files>': [Use(open, error="Files should be readable")]
})
args = schema.validate(args)
最初にSchema
クラスのコンストラクタにスキーマ定義Dictionaryを渡して、
validate
関数でdocoptのDictionaryを渡してやるだけです。
簡単に説明すると
-n
:None
または、int
かつ0以上の値-p
:None
または、float
かつ0.0以上1.0以下の値<files>
: リスト内の全てのファイル名がopen関数でファイルオープンできること- その他 : bool値であること
という定義になっています。Use()
関数を使うと引数に指定した関数を実際に値に適用します。
ここでは<files>
オプションで指定された文字列に対しopen()
関数を実行するので、文字列の代わりにファイルオブジェクトが入ることに注意してください。(ファイルを開きたくない場合は[os.path.exists]
としてファイルの存在を確認するだけで良いと思います。)
スキーマ定義に沿っていなかった場合はSchemaError
例外を吐くので捕まえて終了させます。
スキーマ定義のerror
引数に文字列を与えるとexecpt
でその文字列が渡されます。
try:
args = validate_args(args)
except SchemaError as error:
print(error)
sys.exit(1)
docopt & schema のメリット
標準のモジュールを使わずにdocoptとschemaを使うことのメリットは表記が簡潔になるというのはもちろんですが、 一番のメリットはdocoptによってドキュメントとプログラムが同期している事が保証されるということだと思います。 docoptによって、プログラムを変更してもドキュメントが古いまま・・・というありがちなミスをなくすことができます。 (細かい説明文などは確認しないと古くなってしまう可能性があるので注意しないといけませんが。)
ドキュメントがプログラムによって取得できるというPythonの仕様は自分がPythonすげーと思った最初の仕様ですw