【问题标题】:Customize argparse help message自定义 argparse 帮助消息
【发布时间】:2016-06-21 04:48:56
【问题描述】:

我编写了以下示例代码来演示我的问题。

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-v', '--version', action='version',
                    version='%(prog)s 1.0')
parser.parse_args()

这会产生以下帮助消息。

$ python foo.py --help
usage: foo.py [-h] [-v]

optional arguments:
  -h, --help     show this help message and exit
  -v, --version  show program's version number and exit

我想自定义此帮助输出,使其将所有短语和句子大写,并在句子后面加上句点。换句话说,我希望像这样生成帮助消息。

$ python foo.py --help
Usage: foo.py [-h] [-v]

Optional arguments:
  -h, --help     Show this help message and exit.
  -v, --version  Show program's version number and exit.

这是我可以使用 argparse API 控制的东西吗?如果是这样,怎么做?您能否提供一个小例子来说明如何做到这一点?

【问题讨论】:

  • 你试过设置help吗?
  • 哦,我明白了 - 那么您可以将 add_help 设置为 False 并手动进行。但是小写是这些东西的约定。
  • @jonrsharpe,你怎么知道的? git 大写。 curl 大写。 apt-get 大写。 gcc 大写。如果有约定(我怀疑是否存在),我不确定 argparse 是否在它的右侧。
  • @PaulDraper 我应该更具体一点,它是 argparse 中的约定(一般在 Python 中,python3 --help 显示标志的小写帮助)。
  • 您不能编辑parser.format_help 生成的文本吗?类似parser.usage = edit(parser.format_help()),然后只需编写一个函数edit 来解析每一行,将帮助字符串转换为标题格式。

标签: python python-3.x argparse


【解决方案1】:

首先:大写这些短语有违惯例,argparse 并不能真正帮助您轻松更改这些字符串。这里有三类不同的字符串:来自帮助格式化程序的样板文本、部分标题和每个特定选项的帮助文本。所有这些字符串都是可本地化的;您可以通过gettext() module support 为所有这些字符串提供“大写”翻译。也就是说,如果您有足够的决心和read the source code a little,您可以访问并替换所有这些字符串。

version 操作包含默认的 help 文本,但您可以通过设置 help 参数来提供自己的文本。这同样适用于help 操作;如果您将add_help argument 设置为False,您可以手动添加该操作:

parser = argparse.ArgumentParser(add_help=False)

parser.add_argument('-v', '--version', action='version',
                    version='%(prog)s 1.0', help="Show program's version number and exit.")
parser.add_argument('-h', '--help', action='help', default=argparse.SUPPRESS,
                    help='Show this help message and exit.')

接下来,optional arguments 消息是一个组标题;每个解析器都有两个默认组,一个用于位置参数,另一个用于可选。您可以通过_positionals_optionals 属性来访问它们,这两个属性都具有title 属性:

parser._positionals.title = 'Positional arguments'
parser._optionals.title = 'Optional arguments'

警告,通过访问以下划线开头的名称,您正在冒险进入模块的未记录的私有 API,并且您的代码可能会在未来的更新中中断。

最后,要更改usage 字符串,您必须继承帮助格式化程序;将子类作为formatter_class argument 传递:

class CapitalisedHelpFormatter(argparse.HelpFormatter):
    def add_usage(self, usage, actions, groups, prefix=None):
        if prefix is None:
            prefix = 'Usage: '
        return super(CapitalisedHelpFormatter, self).add_usage(
            usage, actions, groups, prefix)

parser = argparse.ArgumentParser(formatter_class=CapitalisedHelpFormatter)

演示,将所有这些放在一起:

>>> import argparse
>>> class CapitalisedHelpFormatter(argparse.HelpFormatter):
...     def add_usage(self, usage, actions, groups, prefix=None):
...         if prefix is None:
...             prefix = 'Usage: '
...         return super(CapitalisedHelpFormatter, self).add_usage(
...             usage, actions, groups, prefix)
...
>>> parser = argparse.ArgumentParser(add_help=False, formatter_class=CapitalisedHelpFormatter)
>>> parser._positionals.title = 'Positional arguments'
>>> parser._optionals.title = 'Optional arguments'
>>> parser.add_argument('-v', '--version', action='version',
...                     version='%(prog)s 1.0', help="Show program's version number and exit.")
_VersionAction(option_strings=['-v', '--version'], dest='version', nargs=0, const=None, default='==SUPPRESS==', type=None, choices=None, help="Show program's version number and exit.", metavar=None)
>>> parser.add_argument('-h', '--help', action='help', default=argparse.SUPPRESS,
...                     help='Show this help message and exit.')
_HelpAction(option_strings=['-h', '--help'], dest='help', nargs=0, const=None, default='==SUPPRESS==', type=None, choices=None, help='Show this help message and exit.', metavar=None)
>>> print(parser.format_help())
Usage: [-v] [-h]

Optional arguments:
  -v, --version  Show program's version number and exit.
  -h, --help     Show this help message and exit.

【讨论】:

  • 我认为即使argparse.HelpFormatter 的非下划线方法也是私有API:github.com/python/cpython/blob/v3.6.5/Lib/argparse.py#L58 "还要注意HelpFormatter 和RawDescriptionHelpFormatter 仅被视为公共对象名称——格式化程序对象的API仍被视为实现细节。”
  • 正如 Paul Draper 在单独的评论中指出的那样,对于命令行程序的大小写并没有真正的约定,因此您的开场白夸大了情况。对 git、curl、vim、gcc、perl、ruby、diff、rsync 运行 --help 以查看各种方法。甚至 python 本身也采用了与 argparse 略有不同的路径:大部分都是小写,但有一些偏差(“选项和参数”标题,可以说是 -X dev 要点)。
  • @FMc:我并没有过多地谈论流行的命令行工具中常见的东西,而是更多地谈论英语语法的约定。流行的 OSS 工具背后的程序员来自非常广泛的文化背景,他们对英语及其(有时令人费解的)大写规则的掌握程度各不相同,因此存在很大的可变性也就不足为奇了。
【解决方案2】:

Martijn 提供了一些想到的修复方法 - 提供 help 参数和自定义 Formatter 类。

另一个部分修复是在创建参数后修改帮助字符串。 add_argument 创建并返回一个包含参数和默认值的 Action 对象。您可以保存指向此的链接,并修改Action。您还可以获取这些操作的列表,然后采取行动。

让我举例说明,对于具有默认帮助和另一个参数的简单解析器,操作列表是:

In [1064]: parser._actions
Out[1064]: 
[_HelpAction(option_strings=['-h', '--help'], dest='help', nargs=0, const=None, default='==SUPPRESS==', type=None, choices=None, help='show this help message and exit', metavar=None),
 _StoreAction(option_strings=['-f', '--foo'], dest='foo', nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None)]

我可以查看和修改其中任何一个的help 属性:

In [1065]: parser._actions[0].help
Out[1065]: 'show this help message and exit'
In [1066]: parser._actions[0].help='Show this help message and exit.'

生成此帮助:

In [1067]: parser.parse_args(['-h'])
usage: ipython3 [-h] [-f FOO]    
optional arguments:
  -h, --help         Show this help message and exit.
  -f FOO, --foo FOO

使用parser._actions 列表使用“私有”属性,有些人认为这是不明智的。但是在 Python 中,公共/私有的区别并不严格,可以小心地打破。 Martijn 通过访问 parser._positionals.title 来做到这一点。

更改该组标题的另一种方法是使用自定义参数组

ogroup=parser.add_argument_group('Correct Optionals Title')
ogroup.add_argument('-v',...)
ogroup.add_argument('-h',...)

【讨论】:

    【解决方案3】:

    编辑:如果已经显着扩展此功能并将继续在GitHub 上这样做。为了保持一致性,我将在此处保留我的答案。


    不依赖于内部 API(如有更改,恕不另行通知),这里有一个仅使用公共 API 的替代方案。可以说它更复杂,但反过来让您可以最大程度地控制打印的内容:

    class ArgumentParser(argparse.ArgumentParser):
    
        def __init__(self, *args, **kwargs):
            super(ArgumentParser, self).__init__(*args, **kwargs)
            self.program = { key: kwargs[key] for key in kwargs }
            self.options = []
    
        def add_argument(self, *args, **kwargs):
            super(ArgumentParser, self).add_argument(*args, **kwargs)
            option = {}
            option["flags"] = [ item for item in args ]
            for key in kwargs:
                option[key] = kwargs[key]
            self.options.append(option)
    
        def print_help(self):
            # use self.program/self.options to produce custom help text
    

    它是如何工作的:

    • 点击argparse.ArgumentParser的构造函数来捕获和存储程序信息(例如描述、用法)
    • 点击argparse.ArgumentParser.add_argument() 以捕获和存储添加的参数(例如标志、帮助、默认值)
    • 重新定义 argparse.ArgumentParser.print_help() 并使用之前存储的程序信息/参数来生成帮助文本

    这是一个涵盖一些常见用例的完整示例。请注意,它绝不是完整的(例如,不支持位置参数或具有多个参数的选项),但应该提供一种可能的印象:

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    
    import os
    import sys
    import argparse
    import textwrap
    
    class ArgumentParser(argparse.ArgumentParser):
    
        def __init__(self, *args, **kwargs):
            super(ArgumentParser, self).__init__(*args, **kwargs)
            self.program = { key: kwargs[key] for key in kwargs }
            self.options = []
    
        def add_argument(self, *args, **kwargs):
            super(ArgumentParser, self).add_argument(*args, **kwargs)
            option = {}
            option["flags"] = [ item for item in args ]
            for key in kwargs:
                option[key] = kwargs[key]
            self.options.append(option)
    
        def print_help(self):
            wrapper = textwrap.TextWrapper(width=80)
    
            # Print usage
            if "usage" in self.program:
                print("Usage: %s" % self.program["usage"])
            else:
                usage = []
                for option in self.options:
                    usage += [ "[%s %s]" % (item, option["metavar"]) if "metavar" in option else "[%s %s]" % (item, option["dest"].upper()) if "dest" in option else "[%s]" % item for item in option["flags"] ]
                wrapper.initial_indent = "Usage: %s " % os.path.basename(sys.argv[0])
                wrapper.subsequent_indent = len(wrapper.initial_indent) * " "
                output = str.join(" ", usage)
                output = wrapper.fill(output)
                print(output)
            print()
    
            # Print description
            if "description" in self.program:
                print(self.program["description"])
                print()
    
            # Print options
            print("Options:")
            maxlen = 0
            for option in self.options:
                option["flags2"] = str.join(", ", [ "%s %s" % (item, option["metavar"]) if "metavar" in option else "%s %s" % (item, option["dest"].upper()) if "dest" in option else item for item in option["flags"] ])
                if len(option["flags2"]) > maxlen:
                    maxlen = len(option["flags2"])
            for option in self.options:
                template = "  %-" + str(maxlen) + "s  "
                wrapper.initial_indent = template % option["flags2"]
                wrapper.subsequent_indent = len(wrapper.initial_indent) * " "
                if "help" in option and "default" in option:
                    output = option["help"]
                    output += " (default: '%s')" % option["default"] if isinstance(option["default"], str) else " (default: %s)" % str(option["default"])
                    output = wrapper.fill(output)
                elif "help" in option:
                    output = option["help"]
                    output = wrapper.fill(output)
                elif "default" in option:
                    output = "Default: '%s'" % option["default"] if isinstance(option["default"], str) else "Default: %s" % str(option["default"])
                    output = wrapper.fill(output)
                else:
                    output = wrapper.initial_indent
                print(output)
    
    # Main
    if (__name__ == "__main__"):
        #parser = argparse.ArgumentParser(description="Download program based on some library.", argument_default=argparse.SUPPRESS, allow_abbrev=False, add_help=False)
        #parser = argparse.ArgumentParser(usage="%s [OPTION]..." % os.path.basename(sys.argv[0]), description="Download program based on some library.", argument_default=argparse.SUPPRESS, allow_abbrev=False, add_help=False)
        #parser = ArgumentParser(usage="%s [OPTION]..." % os.path.basename(sys.argv[0]), description="Download program based on some library.", argument_default=argparse.SUPPRESS, allow_abbrev=False, add_help=False)
        parser = ArgumentParser(description="Download program based on some library.", argument_default=argparse.SUPPRESS, allow_abbrev=False, add_help=False)
    
        parser.add_argument("-c", "--config-file", action="store", dest="config_file", metavar="file", type=str, default="config.ini")
        parser.add_argument("-d", "--database-file", action="store", dest="database_file", metavar="file", type=str, help="SQLite3 database file to read/write", default="database.db")
        parser.add_argument("-l", "--log-file", action="store", dest="log_file", metavar="file", type=str, help="File to write log to", default="debug.log")
        parser.add_argument("-f", "--data-file", action="store", dest="data_file", metavar="file", type=str, help="Data file to read", default="data.bin")
        parser.add_argument("-t", "--threads", action="store", dest="threads", type=int, help="Number of threads to spawn", default=3)
        parser.add_argument("-p", "--port", action="store", dest="port", type=int, help="TCP port to listen on for access to the web interface", default="12345")
        parser.add_argument("--max-downloads", action="store", dest="max_downloads", metavar="value", type=int, help="Maximum number of concurrent downloads", default=5)
        parser.add_argument("--download-timeout", action="store", dest="download_timeout", metavar="value", type=int, help="Download timeout in seconds", default=120)
        parser.add_argument("--max-requests", action="store", dest="max_requests", metavar="value", type=int, help="Maximum number of concurrent requests", default=10)
        parser.add_argument("--request-timeout", action="store", dest="request_timeout", metavar="value", type=int, help="Request timeout in seconds", default=60)
        parser.add_argument("--main-interval", action="store", dest="main_interval", metavar="value", type=int, help="Main loop interval in seconds", default=60)
        parser.add_argument("--thread-interval", action="store", dest="thread_interval", metavar="value", type=int, help="Thread loop interval in milliseconds", default=500)
        parser.add_argument("--console-output", action="store", dest="console_output", metavar="value", type=str.lower, choices=["stdout", "stderr"], help="Output to use for console", default="stdout")
        parser.add_argument("--console-level", action="store", dest="console_level", metavar="value", type=str.lower, choices=["debug", "info", "warning", "error", "critical"], help="Log level to use for console", default="info")
        parser.add_argument("--logfile-level", action="store", dest="logfile_level", metavar="value", type=str.lower, choices=["debug", "info", "warning", "error", "critical"], help="Log level to use for log file", default="info")
        parser.add_argument("--console-color", action="store", dest="console_color", metavar="value", type=bool, help="Colorized console output", default=True)
        parser.add_argument("--logfile-color", action="store", dest="logfile_color", metavar="value", type=bool, help="Colorized log file output", default=False)
        parser.add_argument("--log-template", action="store", dest="log_template", metavar="value", type=str, help="Template to use for log lines", default="[%(created)d] [%(threadName)s] [%(levelname)s] %(message)s")
        parser.add_argument("-h", "--help", action="help", help="Display this message")
    
        args = parser.parse_args(["-h"])
    

    产生的输出:

    Usage: argparse_custom_usage.py [-c file] [--config-file file] [-d file]
                                    [--database-file file] [-l file] [--log-file
                                    file] [-f file] [--data-file file] [-t THREADS]
                                    [--threads THREADS] [-p PORT] [--port PORT]
                                    [--max-downloads value] [--download-timeout
                                    value] [--max-requests value] [--request-timeout
                                    value] [--main-interval value] [--thread-
                                    interval value] [--console-output value]
                                    [--console-level value] [--logfile-level value]
                                    [--console-color value] [--logfile-color value]
                                    [--log-template value] [-h] [--help]
    
    Download program based on some library.
    
    Options:
      -c file, --config-file file    Default: 'config.ini'
      -d file, --database-file file  SQLite3 database file to read/write (default:
                                     'database.db')
      -l file, --log-file file       File to write log to (default: 'debug.log')
      -f file, --data-file file      Data file to read (default: 'data.bin')
      -t THREADS, --threads THREADS  Number of threads to spawn (default: 3)
      -p PORT, --port PORT           TCP port to listen on for access to the web
                                     interface (default: '12345')
      --max-downloads value          Maximum number of concurrent downloads
                                     (default: 5)
      --download-timeout value       Download timeout in seconds (default: 120)
      --max-requests value           Maximum number of concurrent requests (default:
                                     10)
      --request-timeout value        Request timeout in seconds (default: 60)
      --main-interval value          Main loop interval in seconds (default: 60)
      --thread-interval value        Thread loop interval in milliseconds (default:
                                     500)
      --console-output value         Output to use for console (default: 'stdout')
      --console-level value          Log level to use for console (default: 'info')
      --logfile-level value          Log level to use for log file (default: 'info')
      --console-color value          Colorized console output (default: True)
      --logfile-color value          Colorized log file output (default: False)
      --log-template value           Template to use for log lines (default:
                                     '[%(created)d] [%(threadName)s] [%(levelname)s]
                                     %(message)s')
      -h, --help                     Display this message
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-08-17
      • 1970-01-01
      • 2021-09-16
      • 2016-02-27
      • 2014-10-07
      • 2015-07-26
      • 2017-04-04
      • 2016-01-01
      相关资源
      最近更新 更多