Python argparse:很多选择导致丑陋的帮助输出

时间:2012-02-20 18:24:26

标签: python argparse

我有这个代码,我通常很满意:

import argparse

servers = [ "ApaServer", "BananServer", "GulServer", "SolServer", "RymdServer",
            "SkeppServer", "HavsServer", "PiratServer", "SvartServer", "NattServer", "SovServer" ]

parser = argparse.ArgumentParser(description="A program to update components on servers.")
group = parser.add_mutually_exclusive_group()
group.add_argument('-l', '--list', dest="update", action='store_false', default=False, help='list server components')
group.add_argument('-u', '--updatepom', dest="update", action='store_true', help='update server components')
parser.add_argument('-o', '--only', nargs='*', choices=servers, help='Space separated list of case sensitive server names to process')
parser.add_argument('-s', '--skip', nargs='*', choices=servers, help='Space separated list of case sensitive server names to exclude from processing')
args = parser.parse_args()

我喜欢选择=服务器验证输入中的服务器名称,因此我不必这样做。但是,有这么多有效的选择会使帮助输出看起来很糟糕:

usage: args.py [-h] [-l | -u]
               [-o [{ApaServer,BananServer,GulServer,SolServer,RymdServer,SkeppServer,HavsServer,PiratServer,SvartServer,NattServer,SovServer} [{ApaServer,BananServer,GulServer,SolServer,RymdServer,SkeppServer,HavsServer,PiratServer,SvartServer,NattServer,SovServer} ...]]]
               [-s [{ApaServer,BananServer,GulServer,SolServer,RymdServer,SkeppServer,HavsServer,PiratServer,SvartServer,NattServer,SovServer} [{ApaServer,BananServer,GulServer,SolServer,RymdServer,SkeppServer,HavsServer,PiratServer,SvartServer,NattServer,SovServer} ...]]]

A program to update components on servers.

optional arguments:
  -h, --help            show this help message and exit
  -l, --list            list server components
  -u, --updatepom       update server components
  -o [{ApaServer,BananServer,GulServer,SolServer,RymdServer,SkeppServer,HavsServer,PiratServer,SvartServer,NattServer,SovServer} [{ApaServer,BananServer,GulServer,SolServer,RymdServer,SkeppServer,HavsServer,PiratServer,SvartServer,NattServer,SovServer} ...]], --only [{ApaServer,BananServer,GulServer,SolServer,RymdServer,SkeppServer,HavsServer,PiratServer,SvartServer,NattServer,SovServer} [{ApaServer,BananServer,GulServer,SolServer,RymdServer,SkeppServer,HavsServer,PiratServer,SvartServer,NattServer,SovServer} ...]]
                        Space separated list of case sensitive server names to
                        process
  -s [{ApaServer,BananServer,GulServer,SolServer,RymdServer,SkeppServer,HavsServer,PiratServer,SvartServer,NattServer,SovServer} [{ApaServer,BananServer,GulServer,SolServer,RymdServer,SkeppServer,HavsServer,PiratServer,SvartServer,NattServer,SovServer} ...]], --skip [{ApaServer,BananServer,GulServer,SolServer,RymdServer,SkeppServer,HavsServer,PiratServer,SvartServer,NattServer,SovServer} [{ApaServer,BananServer,GulServer,SolServer,RymdServer,SkeppServer,HavsServer,PiratServer,SvartServer,NattServer,SovServer} ...]]
                        Space separated list of case sensitive server names to
                        exclude from processing

如果我愿意,你会推荐哪种方式:

  • 很好(主要是)自动生成的帮助输出
  • 验证提供给-o或-s选项的条目位于servers

加成:

  • 是否可以对服务器名称进行不区分大小写的字符串匹配?

追加

我尝试使用michaelfilms建议,其中-o -s选项从上面的输出中删除,并添加了此部分:

server optional arguments:
  Valid server names are: ApaServer, BananServer, GulServer, SolServer,
  RymdServer, SkeppServer, HavsServer, PiratServer, SvartServer,
  NattServer, SovServer

我认为它看起来很不错,但我确实需要为-o-s选项提供帮助,否则用户将不会知道它们。所以我还没有采用这种方法。

7 个答案:

答案 0 :(得分:44)

我基本上重复了Ernest所说的 - 为了避免丑陋的长选择列表,为基于选择的参数设置metavar =''(尽管它不会消除参数和逗号之间的空格(例如{ {1}}而不是-o ,。。然后,您可以在一般描述中详细描述可用的选择(如果您希望它们列出明显的缩进,RawDescriptionHelpFormatter在这里很有用。)

我不明白为什么欧内斯特的答案被否决了。这段代码

-o,

生成以下帮助输出

import argparse

servers = [ "ApaServer", "BananServer", "GulServer", "SolServer", "RymdServer",
            "SkeppServer", "HavsServer", "PiratServer", "SvartServer", "NattServer", "SovServer" ]

parser = argparse.ArgumentParser(description="A program to update components on servers.")
group = parser.add_mutually_exclusive_group()
group.add_argument('-l', '--list', dest="update", action='store_false', default=False, help='list server components')
group.add_argument('-u', '--updatepom', dest="update", action='store_true', help='update server components')
parser.add_argument('-o', '--only', choices=servers, help='Space separated list of case sensitive server names to process.  Allowed values are '+', '.join(servers), metavar='')
parser.add_argument('-s', '--skip', choices=servers, help='Space separated list of case sensitive server names to exclude from processing.  Allowed values are '+', '.join(servers), metavar='')
args = parser.parse_args()

这是希望原始帖子正在寻找的内容。

答案 1 :(得分:23)

没有必要继承任何东西。只需传递一个metavar参数,其中包含您希望在帮助消息中显示的字符串。

有关详细信息,请参阅argparse documentation

答案 2 :(得分:9)

我有同样的问题,作为解决方法,我使用了epilog来描述每个选项。我不得不使用argparse.RawTextHelpFormatter,它允许你指定epilog是预先格式化的。

def choicesDescriptions():
   return """
Choices supports the following: 
   choice1         - the FIRST option
   choice2         - the SECOND option
   ...
   choiceN         - the Nth option
"""

def getChoices():
   return ["choice1", "choice2", ..., "choiceN"]

parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, epilog=choicesDescriptions())
parser.add_argument(
   'choices', 
   choices=getChoices(),
   help='Arg choice.  See the choices options below'
   )

args = parser.parse_args()
print(args)

答案 3 :(得分:4)

要获得预期的输出,您需要继承argparse.HelpFormatter并实现所需的格式。特别是,您需要实现自己的_metavar_formatter方法,该方法负责将所有选项加入由逗号分隔的单个字符串中。

答案 4 :(得分:4)

这在选项列表非常长的情况下会有所帮助,就像在原始问题中一样,但对于像我这样的人来说,遇到这个问题的人正在寻找一种方法来将中等长度的选项字符串分成两行,这是我的解决方案:

import argparse

class CustomFormatter(argparse.HelpFormatter):
    """Custom formatter for setting argparse formatter_class. Identical to the
    default formatter, except that very long option strings are split into two
    lines.
    """

    def _format_action_invocation(self, action):
        if not action.option_strings:
            metavar, = self._metavar_formatter(action, action.dest)(1)
            return metavar
        else:
            parts = []
            # if the Optional doesn't take a value, format is:
            #    -s, --long
            if action.nargs == 0:
                parts.extend(action.option_strings)
            # if the Optional takes a value, format is:
            #    -s ARGS, --long ARGS
            else:
                default = action.dest.upper()
                args_string = self._format_args(action, default)
                for option_string in action.option_strings:
                    parts.append('%s %s' % (option_string, args_string))
            if sum(len(s) for s in parts) < self._width - (len(parts) - 1) * 2:
                return ', '.join(parts)
            else:
                return ',\n  '.join(parts)

此代码会覆盖默认的argparse.HelpFormatter方法_format_action_invocation,并且与默认实现相同,但最后四行除外。

默认格式化程序行为:

parser = argparse.ArgumentParser(description="Argparse default formatter.")
parser.add_argument('-a', '--argument', help='not too long')
parser.add_argument('-u', '--ugly', choices=range(20), help='looks messy')
parser.print_help()

输出:

usage: test.py [-h] [-a ARGUMENT]
               [-u {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19}]

Argparse default formatter.

optional arguments:
  -h, --help            show this help message and exit
  -a ARGUMENT, --argument ARGUMENT
                        not too long
  -u {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19}, --ugly {0,1,2,3,4,5,6,
7,8,9,10,11,12,13,14,15,16,17,18,19}
                        looks messy

自定义格式化程序行为:

parser = argparse.ArgumentParser(description="Argparse custom formatter.",
                                 formatter_class=CustomFormatter)
parser.add_argument('-a', '--argument', help='not too long')
parser.add_argument('-l', '--less-ugly', choices=range(20), help='less messy')

输出:

usage: test.py [-h] [-a ARGUMENT]
               [-l {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19}]

Argparse custom formatter.

optional arguments:
  -h, --help            show this help message and exit
  -a ARGUMENT, --argument ARGUMENT
                        not too long
  -l {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19},
  --less-ugly {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19}
                        less messy

答案 5 :(得分:3)

为什么不使用parser.add_argument_group为基于服务器的选项创建一个组,并给出一个描述arg显示可能的选择列表?然后将argparse.SUPPRESS传递给每个单独选项的帮助。我相信这会给你你想要的东西。

答案 6 :(得分:1)

http://bugs.python.org/issue16468 argparse only supports iterable choices是讨论选择格式的错误问题。选项列表可以显示在3个位置:使用行,帮助行和错误消息。

解析器所关心的只是进行in__contains__)测试。但是对于格式化,长列表,无界“列表”(例如整数> 100)以及其他不可迭代的对象会产生问题。 metavar是当前用户可以解决大多数格式问题的方式(它可能对错误消息没有帮助)。请查看问题,了解如何更改自己的argparse版本。