Argparse チュートリアル

author:Tshepang Lekhonkhobe

このチュートリアルでは、argparse を丁寧に説明します。argparse は、Python 標準ライブラリの一部であり、おすすめのコマンドライン引数の解析モジュールです。

注釈

同じタスクを満足するモジュールとして、 getopt (C言語の getopt() と同等) と廃止予定の optparse と呼ばれる二つのモジュールがあります。 argparseoptparse をベースにしているので、使用方法がよく似ています。

コンセプト

ls コマンドを使って、このチュートリアルで私たちが学ぶ機能をいくつか見てみましょう:

$ ls
cpython  devguide  prog.py  pypy  rm-unused-function.patch
$ ls pypy
ctypes_configure  demo  dotviewer  include  lib_pypy  lib-python ...
$ ls -l
total 20
drwxr-xr-x 19 wena wena 4096 Feb 18 18:51 cpython
drwxr-xr-x  4 wena wena 4096 Feb  8 12:04 devguide
-rwxr-xr-x  1 wena wena  535 Feb 19 00:05 prog.py
drwxr-xr-x 14 wena wena 4096 Feb  7 00:59 pypy
-rw-r--r--  1 wena wena  741 Feb 18 01:01 rm-unused-function.patch
$ ls --help
Usage: ls [OPTION]... [FILE]...
List information about the FILEs (the current directory by default).
Sort entries alphabetically if none of -cftuvSUX nor --sort is specified.
...

上の4つの実行結果から、以下のことが分かります:

  • ls コマンドは、まったくオプションを指定せずに実行したとしても役に立ちます。デフォルトの動作は、カレントディレクトリの内容を表示することです。

  • デフォルトの動作で提供される以上のことをしたい場合、すこしだけオプションを指定する必要があります。別のディレクトリ pypy を表示したい場合、私たちがしたことは、位置指定引数 (positional argument) として知られる引数を指定することです。これは、引数がコマンドラインのどの位置に現れたかということだけを基に、プログラムがその値について何をするのか分かるべきなので、このように名づけられています。このコンセプトは cp のようなコマンドで重要な意味があります。 cp コマンドのもっとも基本的な使い方は、 cp SRC DEST です。最初の引数は 何をコピーしたいか であり、二つ目の引数は どこにコピーしたいか を意味します。

  • プログラムの振る舞いを変えましょう。例では、単にファイル名を表示する代わりにそれぞれのファイルに関する多くの情報を表示します。このケースでは、-l はoptional引数として知られます。

  • ヘルプテキストの抜粋です。この実行の仕方は、まだ使用したことがないプログラムにたいして行うと有用で、ヘルプテキストを読むことで、プログラムがどのように動作するかわかります。

基礎

(ほとんど)何もしない、とても簡単な例から始めましょう:

import argparse
parser = argparse.ArgumentParser()
parser.parse_args()

下記がこのコードを実行した結果です:

$ python3 prog.py
$ python3 prog.py --help
usage: prog.py [-h]

optional arguments:
  -h, --help  show this help message and exit
$ python3 prog.py --verbose
usage: prog.py [-h]
prog.py: error: unrecognized arguments: --verbose
$ python3 prog.py foo
usage: prog.py [-h]
prog.py: error: unrecognized arguments: foo

こんなことが起こりました:

  • オプションなしでスクリプトを実行した結果、なにも標準出力に表示されませんでした。それほど便利ではありませんね。

  • 二つ目の実行結果から argparse モジュールの有用性がわかります。ほとんど何もしていないのに、すてきなヘルプメッセージが手に入りました。

  • --help (これは -h と短縮できます)だけが無料のオプションです(つまりプログラムで指示する必要はありません)。他のオプションを指定するとエラーになります。エラー時の有用な用法メッセージも、プログラムで指示することなく出力できます。

位置指定引数の導入

以下に例を示します:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("echo")
args = parser.parse_args()
print(args.echo)

このコードを実行してみましょう:

$ python3 prog.py
usage: prog.py [-h] echo
prog.py: error: the following arguments are required: echo
$ python3 prog.py --help
usage: prog.py [-h] echo

positional arguments:
  echo

optional arguments:
  -h, --help  show this help message and exit
$ python3 prog.py foo
foo

こんなことが起こりました:

  • add_argument() メソッドを追加しました。このメソッドはプログラムがどんなコマンドラインオプションを受け付けるか指定するためのものです。このケースではオプションにその機能と合うように echo と名付けました。

  • プログラムを実行すると、オプションを指定するように要求されます。

  • parse_args() メソッドは指定されたオプションのデータを返します。このケースでは echo です。

  • この変数は argparse が自動的に行うある種の魔法です(つまり、値を格納する変数を指定する必要がありません)。変数の名前がメソッドに与えた文字列引数 echo と同じことに気付いたでしょう。

ヘルプメッセージは十分なように見えますが、それほど分かりやすくもありません。たとえば、echo は位置指定引数であることがわかりますが、それが何であるかを知るためには推測するかソースコードを見なけれなりません。もうすこしヘルプメッセージ分かりやすくしてみましょう:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("echo", help="echo the string you use here")
args = parser.parse_args()
print(args.echo)

修正した結果は以下のようになります:

$ python3 prog.py -h
usage: prog.py [-h] echo

positional arguments:
  echo        echo the string you use here

optional arguments:
  -h, --help  show this help message and exit

次は、もっと有益なことをしてみませんか?:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", help="display a square of a given number")
args = parser.parse_args()
print(args.square**2)

下記がこのコードを実行した結果です:

$ python3 prog.py 4
Traceback (most recent call last):
  File "prog.py", line 5, in <module>
    print(args.square**2)
TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'

上手くいきませんでした。 何も伝えなければ、argparse は与えられたオプションを文字列として扱います。 argparse にオプションの値を整数として扱うように伝えましょう:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", help="display a square of a given number",
                    type=int)
args = parser.parse_args()
print(args.square**2)

下記がこのコードを実行した結果です:

$ python3 prog.py 4
16
$ python3 prog.py four
usage: prog.py [-h] square
prog.py: error: argument square: invalid int value: 'four'

今度は上手くいきました。このプログラムは不正な入力が与えられるとそれを処理せずに、より親切なメッセージを表示して実行を終了します。

Optional引数の導入

So far we have been playing with positional arguments. Let us have a look on how to add optional ones:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--verbosity", help="increase output verbosity")
args = parser.parse_args()
if args.verbosity:
    print("verbosity turned on")

実行してみましょう:

$ python3 prog.py --verbosity 1
verbosity turned on
$ python3 prog.py
$ python3 prog.py --help
usage: prog.py [-h] [--verbosity VERBOSITY]

optional arguments:
  -h, --help            show this help message and exit
  --verbosity VERBOSITY
                        increase output verbosity
$ python3 prog.py --verbosity
usage: prog.py [-h] [--verbosity VERBOSITY]
prog.py: error: argument --verbosity: expected one argument

こんなことが起こりました:

  • プログラムは、--verbosity が指定された場合はなにかしらを表示し、指定されなければ何も表示をしないように書かれています。

  • optional 引数を指定せずにプログラムを実行したときにエラーにならないことから、このオプションの指定が任意(optional)であることがわかります。注意すべき点として、デフォルトでは、optional 引数が使用されないときは、関連する変数(このケースでは、 args.verbosity) の値に None が設定されることです。こうする理由は if 文の真偽判定が偽を返すようにするためです。

  • ヘルプメッセージが少し変わりました。

  • --verbosity オプションを使うには、そのオプションにひとつの値を指定しなければなりません。

上記の例では、--verbosity に任意の整数を取れます。この簡単なプログラムでは、実際には True または False の二つの値だけが有効です。そうなるようにコードを修正してみましょう:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--verbose", help="increase output verbosity",
                    action="store_true")
args = parser.parse_args()
if args.verbose:
    print("verbosity turned on")

実行してみましょう:

$ python3 prog.py --verbose
verbosity turned on
$ python3 prog.py --verbose 1
usage: prog.py [-h] [--verbose]
prog.py: error: unrecognized arguments: 1
$ python3 prog.py --help
usage: prog.py [-h] [--verbose]

optional arguments:
  -h, --help  show this help message and exit
  --verbose   increase output verbosity

こんなことが起こりました:

  • いまや、このオプションは値をとるオプションというよりは、フラグになりました。オプションの名前をその意図に合うように変更しました。新しいキーワード引数 action"store_true" を値として渡していることに注意してください。これはオプションが指定されると args.verboseTrue を代入することを意味しています。もしオプションが指定されなければ代入される値は False になります。

  • フラグは値を取るべきではないので、値を指定するとプログラムはエラーになります。

  • ヘルプテキストが変わっています。

短いオプション

コマンドラインになれていれば、オプションの短いバージョンの話題に触れていないことに気付いたでしょう。それはとても簡単です:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-v", "--verbose", help="increase output verbosity",
                    action="store_true")
args = parser.parse_args()
if args.verbose:
    print("verbosity turned on")

上記のプログラムを実行するとこうなります:

$ python3 prog.py -v
verbosity turned on
$ python3 prog.py --help
usage: prog.py [-h] [-v]

optional arguments:
  -h, --help     show this help message and exit
  -v, --verbose  increase output verbosity

新しい機能がヘルプテキストにも反映されている点に気付いたでしょう。

位置指定引数とOptional引数の併用

プログラムが複雑になってきました:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
                    help="display a square of a given number")
parser.add_argument("-v", "--verbose", action="store_true",
                    help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2
if args.verbose:
    print("the square of {} equals {}".format(args.square, answer))
else:
    print(answer)

出力は以下のようになります:

$ python3 prog.py
usage: prog.py [-h] [-v] square
prog.py: error: the following arguments are required: square
$ python3 prog.py 4
16
$ python3 prog.py 4 --verbose
the square of 4 equals 16
$ python3 prog.py --verbose 4
the square of 4 equals 16
  • 位置指定引数を元に戻したので、引数を指定しないとエラーになりました。

  • 2つのオプションの順序を考慮しないことに注意してください。

複数の詳細レベルを値にとれるようにプログラムを元に戻して、指定された値を使ってみましょう:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
                    help="display a square of a given number")
parser.add_argument("-v", "--verbosity", type=int,
                    help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2
if args.verbosity == 2:
    print("the square of {} equals {}".format(args.square, answer))
elif args.verbosity == 1:
    print("{}^2 == {}".format(args.square, answer))
else:
    print(answer)

実行してみましょう:

$ python3 prog.py 4
16
$ python3 prog.py 4 -v
usage: prog.py [-h] [-v VERBOSITY] square
prog.py: error: argument -v/--verbosity: expected one argument
$ python3 prog.py 4 -v 1
4^2 == 16
$ python3 prog.py 4 -v 2
the square of 4 equals 16
$ python3 prog.py 4 -v 3
16

プログラムのバグである最後の結果を除いて、上手く行っているようです。このバグを、--verbosity オプションが取れる値を制限することで修正しましょう:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
                    help="display a square of a given number")
parser.add_argument("-v", "--verbosity", type=int, choices=[0, 1, 2],
                    help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2
if args.verbosity == 2:
    print("the square of {} equals {}".format(args.square, answer))
elif args.verbosity == 1:
    print("{}^2 == {}".format(args.square, answer))
else:
    print(answer)

実行してみましょう:

$ python3 prog.py 4 -v 3
usage: prog.py [-h] [-v {0,1,2}] square
prog.py: error: argument -v/--verbosity: invalid choice: 3 (choose from 0, 1, 2)
$ python3 prog.py 4 -h
usage: prog.py [-h] [-v {0,1,2}] square

positional arguments:
  square                display a square of a given number

optional arguments:
  -h, --help            show this help message and exit
  -v {0,1,2}, --verbosity {0,1,2}
                        increase output verbosity

変更がエラーメッセージとヘルプメッセージの両方に反映されていることに注意してください。

詳細レベルついて、違ったアプローチを試してみましょう。これは CPython 実行可能ファイルがその詳細レベル引数を扱う方法と同じです。( python --help の出力を確認してください):

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
                    help="display the square of a given number")
parser.add_argument("-v", "--verbosity", action="count",
                    help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2
if args.verbosity == 2:
    print("the square of {} equals {}".format(args.square, answer))
elif args.verbosity == 1:
    print("{}^2 == {}".format(args.square, answer))
else:
    print(answer)

もう一つの action である “count” を紹介します。これは指定されたオプションの出現回数をカウントします:

$ python3 prog.py 4
16
$ python3 prog.py 4 -v
4^2 == 16
$ python3 prog.py 4 -vv
the square of 4 equals 16
$ python3 prog.py 4 --verbosity --verbosity
the square of 4 equals 16
$ python3 prog.py 4 -v 1
usage: prog.py [-h] [-v] square
prog.py: error: unrecognized arguments: 1
$ python3 prog.py 4 -h
usage: prog.py [-h] [-v] square

positional arguments:
  square           display a square of a given number

optional arguments:
  -h, --help       show this help message and exit
  -v, --verbosity  increase output verbosity
$ python3 prog.py 4 -vvv
16
  • 前のバージョンのスクリプトのようにフラグ (action="store_true" に似ています)になりました。エラーとなる理由がわかります。(訳注: 5つ目の例では -v オプションに引数を与えたため、エラーとなっています。)

  • これは”store_true” アクションによく似た動作をします。

  • では “count” アクションが何をもたらすかデモンストレーションをします。おそらくこのような使用方法を前に見たことがあるでしょう。

  • -v フラグを指定しなければ、フラグの値は None とみなされます。

  • 期待通り、長い形式のフラグを指定しても同じ結果になります。

  • 残念ながら、このヘルプ出力はプログラムが新しく得た機能についてそこまで有益ではありません。しかし、スクリプトのドキュメンテーションを改善することでいつでも修正することができます(例えば、help キーワード引数を使用することで)。

  • 最後の出力はプログラムにバグがあることを示します。

修正しましょう:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
                    help="display a square of a given number")
parser.add_argument("-v", "--verbosity", action="count",
                    help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2

# bugfix: replace == with >=
if args.verbosity >= 2:
    print("the square of {} equals {}".format(args.square, answer))
elif args.verbosity >= 1:
    print("{}^2 == {}".format(args.square, answer))
else:
    print(answer)

これが結果です:

$ python3 prog.py 4 -vvv
the square of 4 equals 16
$ python3 prog.py 4 -vvvv
the square of 4 equals 16
$ python3 prog.py 4
Traceback (most recent call last):
  File "prog.py", line 11, in <module>
    if args.verbosity >= 2:
TypeError: unorderable types: NoneType() >= int()
  • 最初の出力は上手くいっていますし、以前のバグが修正されています。最も詳細な出力を得るには、2 以上の値が必要です。

  • 三番目の結果は、よくありません。

このバグを修正しましょう:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
                    help="display a square of a given number")
parser.add_argument("-v", "--verbosity", action="count", default=0,
                    help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2
if args.verbosity >= 2:
    print("the square of {} equals {}".format(args.square, answer))
elif args.verbosity >= 1:
    print("{}^2 == {}".format(args.square, answer))
else:
    print(answer)

もう一つのキーワード引数である default を導入しました。整数値と比較できるように、その値に 0 を設定しました。デフォルトでは、optional 引数が指定されていない場合 None となること、None が整数値と比較できない(よって TypeError 例外となる)ことを思い出してください。

こうなりました:

$ python3 prog.py 4
16

ここまで学んできたことだけで、さまざまな事が実現できます。しかしまだ表面をなぞっただけです。 argparse モジュールはとても強力ですので、チュートリアルを終える前にもう少しだけ探検してみましょう.

もうちょっとだけ学ぶ

もし、この小さなプログラムを二乗以外の累乗が行えるように拡張するとどうなるでしょうか:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("x", type=int, help="the base")
parser.add_argument("y", type=int, help="the exponent")
parser.add_argument("-v", "--verbosity", action="count", default=0)
args = parser.parse_args()
answer = args.x**args.y
if args.verbosity >= 2:
    print("{} to the power {} equals {}".format(args.x, args.y, answer))
elif args.verbosity >= 1:
    print("{}^{} == {}".format(args.x, args.y, answer))
else:
    print(answer)

出力:

$ python3 prog.py
usage: prog.py [-h] [-v] x y
prog.py: error: the following arguments are required: x, y
$ python3 prog.py -h
usage: prog.py [-h] [-v] x y

positional arguments:
  x                the base
  y                the exponent

optional arguments:
  -h, --help       show this help message and exit
  -v, --verbosity
$ python3 prog.py 4 2 -v
4^2 == 16

これまで、出力されるテキストを 変更する ために詳細レベルを使ってきました。かわりに下記の例では、追加の テキストを出力するのに詳細レベルを使用します:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("x", type=int, help="the base")
parser.add_argument("y", type=int, help="the exponent")
parser.add_argument("-v", "--verbosity", action="count", default=0)
args = parser.parse_args()
answer = args.x**args.y
if args.verbosity >= 2:
    print("Running '{}'".format(__file__))
if args.verbosity >= 1:
    print("{}^{} == ".format(args.x, args.y), end="")
print(answer)

出力:

$ python3 prog.py 4 2
16
$ python3 prog.py 4 2 -v
4^2 == 16
$ python3 prog.py 4 2 -vv
Running 'prog.py'
4^2 == 16

競合するオプション

ここまでで、 argparse.ArgumentParser インスタンスの二つのメソッドについて学んできました。では三つ目のメソッド add_mutually_exclusive_group() を紹介しましょう。このメソッドでは、互いに競合するオプションを指定することができます。新しい機能がより意味をなすようにプログラムを変更しましょう: --verbose オプションと反対の --quiet オプションを導入します:

import argparse

parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument("-v", "--verbose", action="store_true")
group.add_argument("-q", "--quiet", action="store_true")
parser.add_argument("x", type=int, help="the base")
parser.add_argument("y", type=int, help="the exponent")
args = parser.parse_args()
answer = args.x**args.y

if args.quiet:
    print(answer)
elif args.verbose:
    print("{} to the power {} equals {}".format(args.x, args.y, answer))
else:
    print("{}^{} == {}".format(args.x, args.y, answer))

プログラムはより簡潔になりましたが、デモのための機能が失われました。ともかく下記が実行結果です:

$ python3 prog.py 4 2
4^2 == 16
$ python3 prog.py 4 2 -q
16
$ python3 prog.py 4 2 -v
4 to the power 2 equals 16
$ python3 prog.py 4 2 -vq
usage: prog.py [-h] [-v | -q] x y
prog.py: error: argument -q/--quiet: not allowed with argument -v/--verbose
$ python3 prog.py 4 2 -v --quiet
usage: prog.py [-h] [-v | -q] x y
prog.py: error: argument -q/--quiet: not allowed with argument -v/--verbose

これは分かりやすいでしょう。ここで得たちょっとした柔軟性を示すために最後の出力を追加しました、つまり長い形式のオプションと短い形式のオプションの混在です。

結びの前に、恐らくあなたはプログラムの主な目的をユーザに伝えたいでしょう。万が一、彼らがそれを知らないときに備えて:

import argparse

parser = argparse.ArgumentParser(description="calculate X to the power of Y")
group = parser.add_mutually_exclusive_group()
group.add_argument("-v", "--verbose", action="store_true")
group.add_argument("-q", "--quiet", action="store_true")
parser.add_argument("x", type=int, help="the base")
parser.add_argument("y", type=int, help="the exponent")
args = parser.parse_args()
answer = args.x**args.y

if args.quiet:
    print(answer)
elif args.verbose:
    print("{} to the power {} equals {}".format(args.x, args.y, answer))
else:
    print("{}^{} == {}".format(args.x, args.y, answer))

使用方法のテキストが少し変化しました。[-v | -q]-v または -q のどちらかを使用できるが、同時に両方を使用できないことを意味します:

$ python3 prog.py --help
usage: prog.py [-h] [-v | -q] x y

calculate X to the power of Y

positional arguments:
  x              the base
  y              the exponent

optional arguments:
  -h, --help     show this help message and exit
  -v, --verbose
  -q, --quiet

結び

argparse モジュールはここで学んだことより多くの機能を提供します。モジュールのドキュメントはとても詳細で、綿密で、そしてたくさんの例があります。このチュートリアルの体験したことで、気がめいることなくそれらの他の機能を会得できるに違いありません。