Python argparse:列出使用中的个别选项

时间:2018-04-22 18:55:44

标签: python argparse

我有一个带有多个参数的程序,例如

breakfast.py --customer=vikings eggs sausage bacon

其中"鸡蛋","香肠"和"培根"可以从特定选择列表中指定。

现在我喜欢breakfast.py --help的输出看起来像这样:

usage: breakfast.py [-h] [--customer CUSTOMER] INGREDIENT [INGREDIENT ...]

positional arguments:
  your choice of ingredients:
    bacon              Lovely bacon
    egg                The runny kind
    sausage            Just a roll
    spam               Glorious SPAM
    tomato             Sliced and diced

optional arguments:
  -h, --help           show this help message and exit
  --customer CUSTOMER  salutation for addressing the customer

我尝试了两种方法,但到目前为止我都失败了。

使用参数选择:

import argparse

parser = argparse.ArgumentParser()

toppings = {
    'bacon': "Lovely bacon",
    'egg': 'The runny kind',
    'sausage': 'Just a roll',
    'spam': 'Glorious SPAM',
    'tomato': 'Sliced and diced',
}
parser.add_argument('--customer', default='Mr. and Mrs. Bun', action='store',
                    help='salutation for addressing the customer')
parser.add_argument('ingredients', nargs='+', choices=toppings.keys(),
                    help='your choice of ingredients')

options = parser.parse_args('--customer=Vikings egg sausage bacon'.split())
print("Dear {}, we are happy to serve you {}" \
      .format(options.customer, ', '.join(options.ingredients)))

上述程序的使用打印出没有详细信息的dict格式列表:

usage: breakfast.py [-h] [--customer CUSTOMER]
                      {bacon,egg,sausage,spam,tomato}
                      [{bacon,egg,sausage,spam,tomato} ...]

positional arguments:
  {bacon,egg,sausage,spam,tomato}
                        your choice of ingredients

metavar='INGREDIENT'添加到add_argument('ingredients', ...)并未列出所有选项:

usage: breakfast.py [-h] [--customer CUSTOMER] INGREDIENT [INGREDIENT ...]

positional arguments:
  INGREDIENT           your choice of ingredients

我简单地尝试使用子程序:

import argparse

parser = argparse.ArgumentParser()

parser.add_argument('--customer', default='Mr. and Mrs. Bun', action='store',
                    help='salutation for addressing the customer')
ingredients = parser.add_subparsers(title='your choice of an ingredient',
                    dest='ingredient', metavar='ingredient')
ingredients.add_parser('bacon', help="Lovely bacon")
ingredients.add_parser('egg', help="The runny kind")
ingredients.add_parser('sausage', help="Just a roll")
ingredients.add_parser('spam', help="Glorious SPAM")
ingredients.add_parser('tomato', help="Sliced and diced")

options = parser.parse_args('--customer=Vikings spam'.split())
print("Dear {}, we are happy to serve you {}" \
      .format(options.customer, options.ingredient))

以我喜欢的方式列出用法:

usage: breakfast.py [-h] [--customer CUSTOMER] ingredient ...

optional arguments:
  -h, --help           show this help message and exit
  --customer CUSTOMER  salutation for addressing the customer

your choice of an ingredient:
  ingredient
    bacon              Lovely bacon
    egg                The runny kind
    sausage            Just a roll
    spam               Glorious SPAM
    tomato             Sliced and diced

默认情况下,子程序仅允许选择一个选项。幸运的是this answer shows it is possible to allow multiple subcommands),但这只是为了让格式正确。我最近从argparse转到ConfigArgParse,这种方法在那里失败了。

我认为我最好还原为使用具有多个选项的单个参数,并使用customat格式化。

不幸的是,关于调整argparse格式的文档很少,所以我很感激如何解决这个问题。

3 个答案:

答案 0 :(得分:3)

add_argument更改为:

parser.add_argument('ingredients', nargs='+', choices=toppings.keys(),
                metavar='INGREDIENT',
                help='your choice of ingredients: %(choices)s')

产生

usage: stack49969605.py [-h] [--customer CUSTOMER] INGREDIENT [INGREDIENT ...]

positional arguments:
  INGREDIENT           your choice of ingredients: bacon, egg, sausage, spam,
                       tomato

更改格式化程序:

formatter_class=argparse.RawTextHelpFormatter

以及add_argument help参数:

                    help = """
your choice of ingredients:
bacon              Lovely bacon
egg                The runny kind
sausage            Just a roll
spam               Glorious SPAM
tomato             Sliced and diced
    """
    )

产生

usage: stack49969605.py [-h] [--customer CUSTOMER] INGREDIENT [INGREDIENT ...]

positional arguments:
  INGREDIENT           
                       your choice of ingredients:
                       bacon              Lovely bacon
                       egg                The runny kind
                       sausage            Just a roll
                       spam               Glorious SPAM
                       tomato             Sliced and diced

或者您可以使用argparse.RawDescriptionHelpFormatter,并将格式化的表格放在descriptionepilog中。

另一种选择是创建一个模仿subparsers类方面的Action子类。但这需要更深入地了解如何在格式化中处理此Action类。

class _SubParsersAction(Action):
    class _ChoicesPseudoAction(Action):

subparsers Action对象维护此_choices_actions类的PseudoAction列表,仅仅是为了欺骗帮助格式化程序显示子分析符,就好像它们是一组嵌套的{ {1}}。此列表不用于解析;仅用于帮助格式化。

答案 1 :(得分:2)

根据这里的反馈,我深入研究了argparse代码。使用子分析符的合理解决方案发布在https://stackoverflow.com/a/49977713/428542

此外,我能够找到为每个选项添加伪操作的解决方案,以及修改格式化程序的解决方案。最后,我提出了一个混合解决方案,为每个选项添加伪动作,但是只有格式化程序才能使用它们,通过利用一些实现细节。

第一个解决方案定义了一个自定义操作,其目的是什么都不做,但仍打印一些使用信息。这个NoAction类给出了不同的选项。

import argparse

class NoAction(argparse.Action):
    def __init__(self, **kwargs):
        kwargs.setdefault('default', argparse.SUPPRESS)
        kwargs.setdefault('nargs', 0)
        super(NoAction, self).__init__(**kwargs)
    def __call__(self, parser, namespace, values, option_string=None):
        pass

parser = argparse.ArgumentParser()
parser.register('action', 'none', NoAction)

parser.add_argument('--customer', default='Mr. and Mrs. Bun', action='store',
                 help='salutation for addressing the customer')
parser.add_argument('ingredients', nargs='*', metavar='INGREDIENT',
                 choices=['bacon', 'egg', 'sausage', 'spam', 'tomato'],
                 help='List of ingredients')

group = parser.add_argument_group(title='your choice of ingredients')
group.add_argument('bacon', help="Lovely bacon", action='none')
group.add_argument('egg', help="The runny kind", action='none')
group.add_argument('sausage', help="Just a roll", action='none')
group.add_argument('spam', help="Glorious SPAM", action='none')
group.add_argument('tomato', help="Sliced and diced", action='none')

options = parser.parse_args('--customer=Vikings egg sausage bacon'.split())
print("Dear {}, we are happy to serve you {}" \
      .format(options.customer, ', '.join(options.ingredients)))

options = parser.parse_args(['--help'])

输出:

Dear Vikings, we are happy to serve you egg, sausage, bacon

usage: customchoices.py [-h] [--customer CUSTOMER]
                        [INGREDIENT [INGREDIENT ...]]

positional arguments:
  INGREDIENT           List of ingredients

optional arguments:
  -h, --help           show this help message and exit
  --customer CUSTOMER  salutation for addressing the customer

your choice of ingredients:
  bacon                Lovely bacon
  egg                  The runny kind
  sausage              Just a roll
  spam                 Glorious SPAM
  tomato               Sliced and diced

一个小缺点是个别选择都被添加到成分(用于解析)以及解析器(用于格式化)。我们还可以定义一种方法,直接将选择添加到成分解析器中:

import argparse

class NoAction(argparse.Action):
    def __init__(self, **kwargs):
        kwargs.setdefault('default', argparse.SUPPRESS)
        kwargs.setdefault('nargs', 0)
        super(NoAction, self).__init__(**kwargs)
    def __call__(self, parser, namespace, values, option_string=None):
        pass

class ChoicesAction(argparse._StoreAction):
    def add_choice(self, choice, help=''):
        if self.choices is None:
            self.choices = []
        self.choices.append(choice)
        self.container.add_argument(choice, help=help, action='none')

parser = argparse.ArgumentParser()
parser.register('action', 'none', NoAction)
parser.register('action', 'store_choice', ChoicesAction)

parser.add_argument('--customer', default='Mr. and Mrs. Bun', action='store',
                 help='salutation for addressing the customer')

group = parser.add_argument_group(title='your choice of ingredients')
ingredients = group.add_argument('ingredients', nargs='*', metavar='INGREDIENT',
                 action='store_choice')
ingredients.add_choice('bacon', help="Lovely bacon")
ingredients.add_choice('egg', help="The runny kind")
ingredients.add_choice('sausage', help="Just a roll")
ingredients.add_choice('spam', help="Glorious SPAM")
ingredients.add_choice('tomato', help="Sliced and diced")

上面可能是我最喜欢的方法,尽管有两个动作子类。它只使用公共方法。

另一种方法是修改Formatter。这是可能的,它会将action.choices从列表['option1', 'option2']修改为dict {'option1': 'help_for_option1', 'option2', 'help_for_option2'},并且或多或少地重新实现HelpFormatter._format_action()HelpFormatterWithChoices.format_choices()

import argparse

class HelpFormatterWithChoices(argparse.HelpFormatter):
    def add_argument(self, action):
        if action.help is not argparse.SUPPRESS:
            if isinstance(action.choices, dict):
                for choice, choice_help in action.choices.items():
                    self._add_item(self.format_choices, [choice, choice_help])
            else:
                super(HelpFormatterWithChoices, self).add_argument(action)
    def format_choices(self, choice, choice_help):
        # determine the required width and the entry label
        help_position = min(self._action_max_length + 2,
                            self._max_help_position)
        help_width = max(self._width - help_position, 11)
        action_width = help_position - self._current_indent - 2
        choice_header = choice

        # short choice name; start on the same line and pad two spaces
        if len(choice_header) <= action_width:
            tup = self._current_indent, '', action_width, choice_header
            choice_header = '%*s%-*s  ' % tup
            indent_first = 0

        # long choice name; start on the next line
        else:
            tup = self._current_indent, '', choice_header
            choice_header = '%*s%s\n' % tup
            indent_first = help_position

        # collect the pieces of the choice help
        parts = [choice_header]

        # add lines of help text
        help_lines = self._split_lines(choice_help, help_width)
        parts.append('%*s%s\n' % (indent_first, '', help_lines[0]))
        for line in help_lines[1:]:
            parts.append('%*s%s\n' % (help_position, '', line))

        # return a single string
        return self._join_parts(parts)

parser = argparse.ArgumentParser(formatter_class=HelpFormatterWithChoices)

toppings = {
    'bacon': "Lovely bacon",
    'egg': 'The runny kind',
    'sausage': 'Just a roll',
    'spam': 'Glorious SPAM',
    'tomato': 'Sliced and diced',
}

parser.add_argument('--customer', default='Mr. and Mrs. Bun', action='store',
                 help='salutation for addressing the customer')

group = parser.add_argument_group(title='your choice of ingredients')
ingredients = group.add_argument('ingredients', nargs='*', metavar='INGREDIENT',
                 choices=toppings)

options = parser.parse_args('--customer=Vikings egg sausage bacon'.split())
print("Dear {}, we are happy to serve you {}" \
      .format(options.customer, ', '.join(options.ingredients)))

print()
options = parser.parse_args(['--help'])

输出:

Dear Vikings, we are happy to serve you egg, sausage, bacon

usage: helpformatter.py [-h] [--customer CUSTOMER]
                        [INGREDIENT [INGREDIENT ...]]

optional arguments:
  -h, --help           show this help message and exit
  --customer CUSTOMER  salutation for addressing the customer

your choice of ingredients:
  bacon                Lovely bacon
  egg                  The runny kind
  sausage              Just a roll
  spam                 Glorious SPAM
  tomato               Sliced and diced

应该注意的是,这是唯一一种不打印帮助热线的方法&#34; INGREDIENTS&#34;本身,但只有选择。

然而,我不喜欢这种方法:它重新实现了太多的代码,并依赖于argparse的过多内部实现细节。

还有一种可能的混合方法:argparser中的子分析器代码使用属性action._choices_actions。这通常在_SubParsersAction类中,用于解析和格式化。如果我们使用此属性,但仅用于格式化,该怎么办?

import argparse

class ChoicesAction(argparse._StoreAction):
    def __init__(self, **kwargs):
        super(ChoicesAction, self).__init__(**kwargs)
        if self.choices is None:
            self.choices = []
        self._choices_actions = []
    def add_choice(self, choice, help=''):
        self.choices.append(choice)
        # self.container.add_argument(choice, help=help, action='none')
        choice_action = argparse.Action(option_strings=[], dest=choice, help=help)
        self._choices_actions.append(choice_action)
    def _get_subactions(self):
        return self._choices_actions

parser = argparse.ArgumentParser()
parser.register('action', 'store_choice', ChoicesAction)

parser.add_argument('--customer', default='Mr. and Mrs. Bun', action='store',
                 help='salutation for addressing the customer')

group = parser.add_argument_group(title='your choice of ingredients')
ingredients = group.add_argument('ingredients', nargs='*', metavar='INGREDIENT',
                 action='store_choice')
ingredients.add_choice('bacon', help="Lovely bacon")
ingredients.add_choice('egg', help="The runny kind")
ingredients.add_choice('sausage', help="Just a roll")
ingredients.add_choice('spam', help="Glorious SPAM")
ingredients.add_choice('tomato', help="Sliced and diced")

options = parser.parse_args('--customer=Vikings egg sausage bacon'.split())
print("Dear {}, we are happy to serve you {}" \
      .format(options.customer, ', '.join(options.ingredients)))

print()
options = parser.parse_args(['--help'])

输出:

Dear Vikings, we are happy to serve you egg, sausage, bacon

usage: helpformatter2.py [-h] [--customer CUSTOMER]
                         [INGREDIENT [INGREDIENT ...]]

optional arguments:
  -h, --help           show this help message and exit
  --customer CUSTOMER  salutation for addressing the customer

your choice of ingredients:
  INGREDIENT
    bacon              Lovely bacon
    egg                The runny kind
    sausage            Just a roll
    spam               Glorious SPAM
    tomato             Sliced and diced

这也是一个不错的解决方案,尽管它依赖于_get_subactions()方法的实现细节。

答案 2 :(得分:0)

为什么不自己解析参数而不使用argparser?然后,您可以按照自己喜欢的方式自由格式化帮助屏幕。

import sys

 if sys.argv[1] in ['-h','--help']:
     print "usage: breakfast.py [-h] [--customer CUSTOMER] INGREDIENT [INGREDIENT ...]\n\npositional arguments:\n\tyour choice of ingredients:\n\t\tbacon              Lovely bacon\n\t\tegg                The runny kind\n\t\tsausage            Just a roll\n\t\tspam               Glorious SPAM\n\t\ttomato             Sliced and diced\n\noptional arguments:\n\t-h, --help           show this help message and exit\n\t--customer CUSTOMER  salutation for addressing the customer"

customer_arg = sys.argv[1]
ingrediants = sys.argv[2:len(sys.argv)]
customer = customer_arg.split('=')[1]

这将打印:

usage: breakfast.py [-h] [--customer CUSTOMER] INGREDIENT [INGREDIENT ...]

positional arguments:
        your choice of ingredients:
                bacon              Lovely bacon
                egg                The runny kind
                sausage            Just a roll
                spam               Glorious SPAM
                tomato             Sliced and diced

optional arguments:
        -h, --help           show this help message and exit
        --customer CUSTOMER  salutation for addressing the customer

然后,您可以使用ingrediant列表执行下一步操作。我希望这会有所帮助。