Python argparse:列出用法中的个人选择 [英] Python argparse: list individual choices in the usage
问题描述
我有一个接受多个参数的程序,例如
I have a program which takes multiple arguments, e.g.
breakfast.py --customer=vikings eggs sausage bacon
其中可以从特定选项列表中指定鸡蛋"、香肠"和培根".
where "eggs", "sausage" and "bacon" can be specified from a list of specific choices.
现在我喜欢 breakfast.py --help
的输出看起来像这样:
Now I like the output of breakfast.py --help
to look like this:
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
我尝试了两种方法,但到目前为止都失败了.
I tried two approaches, but so far both failed for me.
使用参数选择:
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)))
上面程序的用法是打印一个没有细节的字典格式的列表:
The usage of the above program prints a dict-formated list without details:
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', ...)
根本没有列出选项:
Adding metavar='INGREDIENT'
to add_argument('ingredients', ...)
does not list the choices at all:
usage: breakfast.py [-h] [--customer CUSTOMER] INGREDIENT [INGREDIENT ...]
positional arguments:
INGREDIENT your choice of ingredients
我曾短暂尝试过使用子程序:
I briefly tried to use subprograms:
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))
以我喜欢的方式列出了用法:
Which does list the usage in the way I like it:
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
默认情况下,子程序只允许选择一个选项.幸运的是这个答案表明可以允许多个子命令),但这感觉就像一个黑客只是为了正确格式化.我最近从 argparse 转移到了 ConfigArgParse,但这种方法在那里失败了.
By default, subprograms only allow one options to be picked. Fortunately this answer shows it is possible to allow multiple subcommands), but this feels like a hack just to get the formatting right. I recently moved from argparse to ConfigArgParse, and this approach failed there.
我认为我最好恢复使用具有多个选择的单个参数,并使用自定义格式.
I think I better revert to using a single argument with multiple choices, and use customat formatting.
不幸的是,关于调整 argparse 格式的文档很少,所以我很感激如何解决这个问题.
Unfortunately, the documentation on adjusting the formatting of argparse is scarce, so I appreciate some help how to approach this.
推荐答案
根据这里的反馈,我深入研究了 argparse 代码.使用子解析器的合理解决方案发布在 https://stackoverflow.com/a/49977713/428542.
Based on the feedback here, I dived into the argparse code. A reasonable solution that uses subparsers is posted at https://stackoverflow.com/a/49977713/428542.
此外,我还找到了为每个选项添加伪操作的解决方案,以及修改格式化程序的解决方案.最后,我提出了一个混合解决方案,通过利用一些实现细节,为每个选项添加伪操作,但只有格式化程序使用它们.
In addition, I was able to find a solution that added a pseudo-action for each option, as well a solution that modified the formatter. Finally I present a hybrid solution that adds pseudo-action for each option, but in such a way that only the formatter uses them, by exploiting some implementation details.
第一种方案定义了一个自定义动作,其目的是什么都不做,但仍然打印一些使用信息.此 NoAction 类提供了不同的选项.
The first solution defines a custom action, whose purpose is to do nothing at all, but still prints some usage information. The different options are given this NoAction class.
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
一个小缺点是单独的选择同时添加到成分(用于解析)和解析器(用于格式化).我们还可以定义一个方法来直接将选项添加到成分解析器中:
A minor disadvantage is that the individual choices are both added to the ingredients (for parsing) as well as to the parser (for formatting). We could also define a method to add the choices to the ingredients parser directly:
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")
以上可能是我最喜欢的方法,尽管有两个动作子类.它只使用公共方法.
The above is probably my favourite method, despite the two action subclasses. It only uses public methods.
另一种方法是修改格式化程序.这是可能的,它将 action.choices 从列表 ['option1', 'option2']
修改为 dict {'option1': 'help_for_option1', 'option2', 'help_for_option2'}
,并且或多或少地将 HelpFormatter._format_action()
重新实现为 HelpFormatterWithChoices.format_choices()
:
An alternative is to modify the Formatter. This is possible, it modifies action.choices from a list ['option1', 'option2']
to a dict {'option1': 'help_for_option1', 'option2', 'help_for_option2'}
, and more-or-less re-implements HelpFormatter._format_action()
as 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
应该注意的是,这是唯一不为INGREDIENTS"本身打印帮助行而只打印选项的方法.
It should be noted that this is the only approach that does not print a help line for "INGREDIENTS" itself, but only the choices.
尽管如此,我不喜欢这种方法:它重新实现了太多代码,并且依赖于 argparse 的太多内部实现细节.
Nevertheless, I dislike this approach: it re-implements too much code, and relies on too much internal implementation details of argparse.
还有一种可能的混合方法:argparser
中的子解析器代码使用属性 action._choices_actions
.这通常在 _SubParsersAction
类中,用于解析和格式化.如果我们使用此属性,但仅用于格式化会怎样?
There is also a hybrid approach possible: the subparser code in argparser
makes use of a property action._choices_actions
. This is normally in the _SubParsersAction
class, both for parsing and for formatting. What if we use this property, but only for formatting?
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()
方法的实现细节.
This is also a nice solution, although it relies on the implementation detail of the _get_subactions()
method.
这篇关于Python argparse:列出用法中的个人选择的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!