2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

argparse:ヘルプのフォーマットクラスを利用して、ヘルプメッセージを改善する(メッセージの改行、デフォルト値/requiredの表示)

Last updated at Posted at 2022-01-21

実行環境

  • Python 3.9.7

はじめに

argparseでCLIを作っています。
デフォルトの設定だと、CLIのヘルプメッセージが分かりづらいです。

例として、以下のCLIのヘルプメッセージを表示してみます。

cli.py
from argparse import ArgumentParser


def parse_args():
    parser = ArgumentParser(
        description=textwrap.dedent(
            """
            イメージを作成する。
            ``aws ec2 create-image`` コマンドを参考にした。
            """
        ).strip()
    )
    parser.add_argument(
        "--name",
        type=str,
        required=True,
        help=textwrap.dedent(
            """
            イメージの名前。
            制約:3-128文字の英数字。
            """
        ).strip(),
    )
    parser.add_argument(
        "--type",
        type=str,
        choices=["foo", "bar"],
        default="bar",
        help=textwrap.dedent(
            """
            イメージのタイプ。
             * foo: FOO
             * bar: BAR
            """
        ).strip(),
    )
    parser.add_argument(
        "--dry-run", action="store_true", help="イメージを作らずに、イメージを作る権限があるかどうかをチェックする。"
    )
    return parser.parse_args()


if __name__ == "__main__":
    parse_args()

textwrap.dedent("""...""").strip()としているのは、ヒアドキュメントの前後の空白を取り除くためです。

$ python cli.py -h
usage: cli.py [-h] --name NAME [--type {foo,bar}] [--dry-run]

イメージを作成する。 ``aws ec2 create-image`` コマンドを参考にした。

optional arguments:
  -h, --help        show this help message and exit
  --name NAME       イメージの名前。 制約:3-128文字の英数字。
  --type {foo,bar}  イメージのタイプ。 * foo: FOO * bar: BAR
  --dry-run         イメージを作らずに、イメージを作る権限があるかどうかをチェックする。

以下の点で、ヘルプメッセージは分かりづらいです。

  • コマンドの説明、引数の説明が改行されていない
  • --typeにデフォルト値が表示されていない

この2点を改善する方法を紹介します。

改善1:フォーマッタークラスを指定する

argparseには4つのフォーマットクラスが用意されています。これらのフォーマットクラスを複数継承したクラスを、ArgumentParserコンストラクタのformatter_class引数に指定します。

from argparse import ArgumentParser, RawTextHelpFormatter, RawDescriptionHelpFormatter, ArgumentDefaultsHelpFormatter

class MyHelpFormatter(RawTextHelpFormatter, RawDescriptionHelpFormatter, ArgumentDefaultsHelpFormatter):
    pass

def parse_args():
    parser = ArgumentParser(
        description="""
        イメージを作成する。
        ``aws ec2 create-image`` コマンドを参考にした。
        """,
        formatter_class=MyHelpFormatter
    )
  • RawTextHelpFormatter: 引数の説明で空白と改行がそのまま表示する。
  • RawDescriptionHelpFormatter: descriptionとepilogで空白と改行がそのまま表示する。
  • ArgumentDefaultsHelpFormatter: デフォルト値を表示する
$ python cli.py -h
usage: cli.py [-h] --name NAME [--type {foo,bar}] [--dry-run]

イメージを作成する。
``aws ec2 create-image`` コマンドを参考にした。

optional arguments:
  -h, --help        show this help message and exit
  --name NAME       イメージの名前。
                    制約:3-128文字の英数字。 (default: None)
  --type {foo,bar}  イメージのタイプ。
                     * foo: FOO
                     * bar: BAR (default: bar)
  --dry-run         イメージを作らずに、イメージを作る権限があるかどうかをチェックする。 (default: False)

コマンドの説明、引数の説明が改行されて、かつデフォルト値も表示されました。

設定2:引数と引数の説明の間に空行を入れる

引数の説明を改行して表示すると、引数と引数の区切りが分かりづらくなります。
なので、引数と引数の説明の間に空行を入れます。

先ほど作ったMyHelpFormatterクラスに_format_actionメソッドを定義して、オーバライドします。
_format_actionメソッドは、argparse.Actionクラスを文字列に変換する関数です。

親クラスの_format_actionメソッドの結果に対して、改行\nを付与することで、引数と引数の説明の間に空行ができます。

class MyHelpFormatter(
    RawTextHelpFormatter, RawDescriptionHelpFormatter, ArgumentDefaultsHelpFormatter
):
    def _format_action(self, action: argparse.Action) -> str:
        return super()._format_action(action) + "\n"
$ python cli.py -h
...

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

  --name NAME       イメージの名前。
                    制約:3-128文字の英数字。 (default: None)

  --type {foo,bar}  イメージのタイプ。
                     * foo: FOO
                     * bar: BAR (default: bar)

  --dry-run         イメージを作らずに、イメージを作る権限があるかどうかをチェックする。 (default: False)

設定3:不要なデフォルト値を表示しない

上記のヘルプメッセージでは、すべての引数に対してデフォルト値が表示されます。
しかし、本当に表示して欲しいデフォルト値は--typeだけです。
--nameのデフォルト値None--dry-runのデフォルト値Falseは自明なので、不要です。

MyHelpFormatterクラスに_get_help_stringメソッドを定義して、オーバライドして対応します。
まずは、ArgumentDefaultsHelpFormatterクラスの_get_help_stringメソッドの中身を、そのまま持ってきます。

ヘルプメッセージにデフォルト値を追加する条件に、action.default is not None and not action.constを追加します。
action.constは、以下の場合にTrueになります。

  • parser.add_argumentのaction引数にstore_truestore_falseを指定したとき
  • parser.add_argumentのconst引数に値を指定したとき

class MyHelpFormatter(
    RawTextHelpFormatter, RawDescriptionHelpFormatter, ArgumentDefaultsHelpFormatter
):
    ...

    def _get_help_string(self, action):
        help = action.help
        if "%(default)" not in action.help:
            if action.default is not SUPPRESS:
                defaulting_nargs = [OPTIONAL, ZERO_OR_MORE]
                if action.option_strings or action.nargs in defaulting_nargs:
                    # カスタム設定
                    if action.default is not None and not action.const:
                        help += " (default: %(default)s)"
        return help
$ python cli.py -h
usage: cli.py [-h] --name NAME [--type {foo,bar}] [--dry-run]

イメージを作成する。
``aws ec2 create-image`` コマンドを参考にした。

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

  --name NAME       イメージの名前。
                    制約:3-128文字の英数字。

  --type {foo,bar}  イメージのタイプ。
                     * foo: FOO
                     * bar: BAR (default: bar)

  --dry-run         イメージを作らずに、イメージを作る権限があるかどうかをチェックする。

--typeのみデフォルト値が表示されました。

設定4:必須かどうかを明記する

コマンドライン引数が必須かどうかは、ヘルプメッセージの"usage"を見れば分かります。

usage: cli.py [-h] --name NAME [--type {foo,bar}] [--dry-run]

しかし、コマンドライン引数が多いと、"usage"は少し分かりづらいです。
そこで、コマンドライン引数の説明に、必須ならrequiredと表示しましょう。
_get_help_stringメソッドに、以下のコードを追記します。

class MyHelpFormatter(
    RawTextHelpFormatter, RawDescriptionHelpFormatter, ArgumentDefaultsHelpFormatter
):

    def _get_help_string(self, action):
        help = action.help
        if action.required:
            help += " (required)"

        if "%(default)" not in action.help:
            ...

        return help

$ python cli.py -h

イメージを作成する。
``aws ec2 create-image`` コマンドを参考にした。

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

  --name NAME       イメージの名前。
                    制約:3-128文字の英数字。 (required)

  --type {foo,bar}  イメージのタイプ。
                     * foo: FOO
                     * bar: BAR (default: bar)

--namerequiredが表示されました。

まとめ

以下のヘルプフォーマットクラスを利用すれば、ヘルプメッセージが見やすくなります。

import argparse
from argparse import (OPTIONAL, SUPPRESS, ZERO_OR_MORE,
                      ArgumentDefaultsHelpFormatter, ArgumentParser,
                      RawDescriptionHelpFormatter, RawTextHelpFormatter)


class MyHelpFormatter(
    RawTextHelpFormatter, RawDescriptionHelpFormatter, ArgumentDefaultsHelpFormatter
):
    def _format_action(self, action: argparse.Action) -> str:
        return super()._format_action(action) + "\n"

    def _get_help_string(self, action):
        help = action.help
        if action.required:
            help += " (required)"
        
        if "%(default)" not in action.help:
            if action.default is not SUPPRESS:
                defaulting_nargs = [OPTIONAL, ZERO_OR_MORE]
                if action.option_strings or action.nargs in defaulting_nargs:
                    if action.default is not None and not action.const:
                        help += " (default: %(default)s)"
        return help

改善できなかったこと

"usage"を適切に改行して見やすくする

コマンドライン引数が多いとき、ヘルプメッセージの"usage"は以下のように引数ごとに改行されれば、見やすくなります。

usage: cli.py [-h] 
 --name NAME
 [--type {foo,bar}]
 [--dry-run]

argparse.HelpFormatterクラスの_format_actions_usageメソッドの以下の部分を修正すれば、改行を入れることができそうです。

text = ' '.join([item for item in parts if item is not None])
# ↓
text = '\n'.join([item for item in parts if item is not None])

_format_actions_usageをオーバライドするには、HelpFormatter._format_actions_usageの中身をMyHelpFormatterクラスに持ってくる必要があります。
しかし、HelpFormatter._format_actions_usageの中身はステップ数が多いため、できればMyHelpFormatterクラスに持ってきたくありません。バグの温床になりそうです。
したがって、この改善は諦めました。

2
2
0

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
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?