はじめに
何らかのコマンド(CLI)を作りたい場合にargparseを使ったりしますね。このargparseをなるべく労力を使わず楽して便利に使うためのtipsを紹介します。
sys.argv
を渡す必要なし
幾つかのargparseの使い方の紹介では、sys.argv
をまじめに渡していますが。不要です。無引数で parse_args()
を呼ぶとよしなにやってくれます。
import argparse
parser = argparse.ArgumentParser()
parser.parse_args() # これでOK
ヘルプメッセージの表示もargparseに任せよう
-h
または --help
をつけるとヘルプメッセージが出る機能はargparseが自動で付けてくれます。そしてその上で、利用方法を間違えてしまった場合の挙動もargparseに委ねてしまうのが便利です。
例えば、何も引数を指定せず実行してしまった場合などのためにこのように親切なコードを書く必要はありません。
import sys
# このコードは不要
if len(sys.argv) == 0:
parser.parse_args(["-h"])
この記事の良いね数が多かったので不要だよということを伝えたかっただけです。
追記:
このコードの意図は、エラー時のメッセージを短いusageではなく、長いhelpをすべて出したいというものだったそうです。少しhackっぽいですが以下の様に書けます(とは言え、処理内容が明示的にわかる元のコードの方が良いという人もいるかもしれません)。
parser.print_usage = parser.print_help
positional argumentの場合
positional argumentを1つでも指定した場合には自動的に引数の不足が調べられるので何もする必要がありません。
import argparse
parser = argparse.ArgumentParser("<command name>")
parser.add_argument("file") # positional argument
args = parser.parse_args()
$ python cmd.py
usage: <command name> [-h] file
<command name>: error: the following arguments are required: file
optionの場合
全てがオプションだった場合には、必須そうなものに required=True
を付けてください(とは言え必須ならpositional argumentするべきという話もあります)。
import argparse
parser = argparse.ArgumentParser("<command name>")
parser.add_argument("--file", required=True)
args = parser.parse_args()
$ python cmd.py
usage: <command name> [-h] --file FILE
<command name>: error: the following arguments are required: file
デフォルトの引数を指定して楽をしよう
何も引数を指定せずに実行してしまった場合の挙動としてもう1つ挙動が考えられます。デフォルト値を埋め込んで処理を行うことです。これは default
で指定してしまえば良いです。
例えば、ファイルを指定しなかった場合には標準入力からデータを受け取るみたいなコマンドを作りたいという場合があるかもしれません。
positional argument の場合
positional argument で指定していた場合には、nargs="?"
を使ってオプショナルにします
import sys
import argparse
parser = argparse.ArgumentParser("hai")
parser.add_argument("file", nargs="?", type=argparse.FileType("r"), default=sys.stdin)
args = parser.parse_args()
print(args.file.read())
こちらが動くようになります。
$ cat hello.txt | python echo.py
$ python echo.py hello.txt
optionの場合
オプション引数の場合にはdefaultを指定するだけです。
parser.add_argument("--file", type=argparse.FileType("r"), default=sys.stdin)
actionを使って楽をしよう
parserのadd_argumentが受け取るactionを覚えておくと便利です。全部は覚える必要はありませんが幾つかのactionは覚えておくと楽ができます。
option引数にはstore_true/store_false
例えば、--with-<something>
みたいなフラグだったり、 --debug
みたいな引数を取らないオプションを取るときにstore_trueを使いましょう。default値はFalseになります
parser.add_argument("--debug", action="store_true")
逆に、--without-<something>
というフラグだったり、defaultをTrueにしてオプションが指定された場合にはFalseになる場合にはstore_falseを使いましょう。
parser.add_argument("--execute", action="store_false") # defaultはdry-runみたいな状況
注意点として with-foo
と without-foo
というフラグの両方を提供したい場合には、set_defaults()でデフォルト値を指定しておいたほうが良いです。そうでないと書いた順序にデフォルトの値が依存してしまうので。
# defaultはdebug=False
parser.add_argument("--without-debug", action="store_false", dest="debug")
parser.add_argument("--with-debug", action="store_true", dest="debug")
parser.set_defaults(debug=False)
同じオプションに複数の値を渡したい場合にはappend
複数の値を取りたい場合にはappendをactionに指定しましょう。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--target", action="append")
args = parser.parse_args()
print(args)
以下のように動きます。
$ python cmd.py --target x --target y --target z
Namespace(target=['x', 'y', 'z'])
取りうる値の範囲を制限したい場合にはchoicesを使おう
取りうる値の範囲を制限したい場合にはchoicesを使うと便利です。例えばloggingのレベルを指定する部分については以下の様に書くと良いです。
import argparse
import logging
parser = argparse.ArgumentParser()
loglevels = list(logging._nameToLevel.keys()) # _ prefixのものなのでお行儀は良くない
parser.add_argument("--loglevel", choices=loglevels, default="INFO")
args = parser.parse_args()
logging.basicConfig(level=args.loglevel)
logger = logging.getLogger("log")
logger.debug("hmm..")
logger.info("hmm")
defaultはinfoが良い気がします。
$ python cmd.py
INFO:log:hmm
$ python cmd.py -h
usage: cmd.py [-h]
[--loglevel {WARN,INFO,NOTSET,DEBUG,CRITICAL,WARNING,ERROR}]
optional arguments:
-h, --help show this help message and exit
--loglevel {WARN,INFO,NOTSET,DEBUG,CRITICAL,WARNING,ERROR}
$ python cmd.py --loglevel=DEBUG
DEBUG:log:hmm..
INFO:log:hmm
argparseのドキュメントを読もう
公式ドキュメントの説明が詳しく役に立つのでこんな記事を読んでいるよりも公式ドキュメントを見た方が有益です。公式ドキュメントを見ましょう。
おまけ
サブコマンド
サブコマンドを作りたい場合には早々にargparseを諦めて、clickを使ってしまいましょう。
関数の定義からコマンドに変換する
後は関数の定義からコマンドとして実行可能にするパッケージを何か1つ覚えておくと便利です(自分は自作しました)。
例えば以下の様なものです。
# greeting.py
from handofcats import as_command
@as_command
def greeting(message, is_surprised=False, name="foo"):
suffix = "!" if is_surprised else ""
print("{name}: {message}{suffix}".format(name=name, message=message, suffix=suffix))
$ pip install handofcats
$ python greeting.py -h
python greeting.py -h
usage: greeting.py [-h] [--is-surprised] [--name NAME] [-v] [-q] message
positional arguments:
message
optional arguments:
-h, --help show this help message and exit
--is-surprised
--name NAME
-v, --verbose (default option: increment logging level(default is
WARNING))
-q, --quiet (default option: decrement logging level(default is
WARNING))
$ python greeting.py
foo: hello
$ python greeting.py --is-surprised --name me hello
me: hello!