LoginSignup
27
28

More than 5 years have passed since last update.

横着な人のための極力労力を使わないargparseの使い方の紹介

Last updated at Posted at 2017-04-29

はじめに

何らかのコマンド(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-foowithout-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!
27
28
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
28