2012年11月6日

Python の argparse モジュールを使う

Python の引数処理モジュールである argparse を使ってみます。 そろそろ Python 3.3 で作業を始めるべきでしょうし、いつまでも Python 2.4 のことを考えていても窮屈なためです。

Python の標準ライブラリを使った引数処理には、次のような段階があると思います。

  • sys.argv を使った原始的な方法
  • getopt モジュールを使った Unix 系の伝統的な方法
  • optparse モジュールを使った Python 2.4 などもターゲットにした方法
  • argparse モジュールを使った最近の方法

Argparse Tutorial には次のように記載されています。

There’s two other modules that fulfill the same task, namely getopt (an equivalent for getopt() from the C language) and the deprecated optparse. Note also that argparse is based on optparse, and therefore very similar in terms of usage.

ざっくり日本語訳 :

同じタスクを満足にこなすためには他にも2つのモジュールがあります。 C 言語の getopt() と等価な getopt と、非推奨になった optparse です。 argparse は optparse をベースにしていますので、使い方は非常に似ています。

その他にもサードパーティのライブラリを使う方法があります。 何らかのフレームワークに付随するものや gflags を使う方法はこちらの記事で少しずつ触れています。

"There should be one-- and preferably only one --obvious way to do it." (* Zen of Python) という精神の通りにはいきませんが、 やはり標準ライブラリに沿って実装する方法を身につけておくことは大切だと思いますので、 argparse を使います。

サンプルスクリプト

早速サンプルスクリプトです。 次の機能を利用しています。

  • 引数処理オブジェクトの初期化
  • オプション引数の設定 (year, month, day, days, format)
  • フラグ引数の設定 (reverse)
  • 出力ファイルの設定 (output)
  • 排他的引数の設定 (verbose, quiet)
  • 実際の引数処理
import argparse
import datetime
import logging
import sys

TODAY = datetime.date.today()


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--year', type=int, default=TODAY.year)
    parser.add_argument('--month', type=int, default=TODAY.month)
    parser.add_argument('--day', type=int, default=TODAY.day)
    parser.add_argument('--format', default='%Y%m%d')
    parser.add_argument('--days', type=int, default=1)
    parser.add_argument('--reverse', default=False, action="store_true")

    parser.add_argument('--output',
        type=argparse.FileType('w'), default=sys.stdout)

    group = parser.add_mutually_exclusive_group()
    group.add_argument("-v", "--verbose", dest="verbose",
                default=False, action="store_true",
                help="set logging to verbose mode")
    group.add_argument("-q", "--quiet", dest="quiet",
                default=False, action="store_true",
                help="set logging to quiet mode")

    args = parser.parse_args()
    if args.verbose:
        logging.basicConfig(level=logging.DEBUG)
    elif not args.quiet:
        logging.basicConfig(level=logging.INFO)

    s = datetime.date(args.year, args.month, args.day)
    logging.debug("Start: %s", s)
    for i in xrange(args.days):
        if args.reverse:
            t = s - datetime.timedelta(days=i)
        else:
            t = s + datetime.timedelta(days=i)
        args.output.write(t.strftime(args.format))
        args.output.write('\n')

if __name__ == '__main__':
    main()


スクリプトの実行

引数無しで実行すると、その日の年月日を出力します。

$ python date-expand.py
20121105

monthday で出力する日付を設定できます。

$ python date-expand.py --month=10 --day=1
20121001

format オプションに日付書式を与えるとフォーマットが変わります。 strftime() が受け付けられないものはエラーになります。

$ python date-expand.py --format="%Y-%m-%d"
2012-11-05

days オプションでその日数分の日付文字列を出力します。 整数以外を与えると引数解析の時点でエラーになります。

$ python date-expand.py --days=7
20121105
20121106
20121107
20121108
20121109
20121110
20121111

action="store_true" を指定するとフラグになります。

$ python date-expand.py --days=7 --reverse
20121105
20121104
20121103
20121102
20121101
20121031
20121030

output で出力ファイルを指定できます。 argparse.FileType('w') を指定することで、自動で書き込みストリームを開いておいてくれます。 存在するディレクトリを指定するとエラーになるなど、ちょっとエラー処理が役に立ちます。 また、デフォルト値に sys.stdout を指定しておくと、標準出力とファイル出力を透過的に扱えます。

$ python date-expand.py --month=9 --day=1 --days=30 --output date.dat
$ head date.dat
20120901
20120902
20120903
20120904
20120905
20120906
20120907
20120908
20120909
20120910

ログ出力を冗長なレベルに設定します。 verbose と quiet は排他的に設定しておきたいので add_mutually_exclusive_group() を使います。 上記のソースコードでは parser.add_argument() ではなく group.add_argument() と呼び出します。 また、オプションの名称はロングオプションだけでなくショートオプションを指定できることも分かります。 (他のオプションも同様)

$ python date-expand.py -v
DEBUG:root:Start: 2012-11-05
20121105

リファレンスなど

まずは 公式ドキュメント を読みたいところですが、パッと見で難解なコード例になっていると思います。 そこで、公式サイトのチュートリアル - Argparse Tutorial - から読み進めるのが分かりやすいでしょう。

また、Python Module Of The Week には "parents" を使った階層化の例があります。 それ以外にも nargs を指定して引数の数を明示する方法、カスタムアクションを実行する方法などがあります。 一通り読んでみると発見が多いと思います。

終わりに

Python の引数処理モジュールである argparse を使ってみました。 argparse の利点は、Python 2.7 と Python 3.x の両方で動作することです。 そろそろ Python 2.4 から Python 2.6 をサポートしないライブラリも登場してきていますし、 よっぽどなユーザーベースがあるコードでない限りは argparse に移行した方が良さそうです。

あと、上記のようなコードを毎回記述するのも冗長なので、 clitool としてモジュールにまとめてみました。 clitool.cli.parse_arguments() と呼び出すことで入出力に関するオプションを設定してくれます。

引数処理以外にもお勉強がてら簡単な処理をまとめていますので、ちょっとしたツールを実装するには便利だと思います。

コメントを投稿