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)))
Использование вышеупомянутой программы печатает отформатированный список без подробностей:
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
По умолчанию подпрограммы позволяют выбирать только одну опцию. К счастью, этот ответ показывает, что можно разрешить несколько подкоманд), но это похоже на хак только для правильного форматирования. Недавно я перешел из argparse в ConfigArgParse, и этот подход потерпел неудачу.
Я думаю, что лучше вернуться к использованию одного аргумента с несколькими вариантами выбора и использовать обычное форматирование.
К сожалению, документации по настройке форматирования argparse недостаточно, поэтому я ценю некоторую помощь, как подойти к этому.
3 ответа
Основываясь на отзывах, я погрузился в код argparse. Разумное решение с использованием подпарсеров размещено по адресу /questions/29426514/kak-proanalizirovat-neskolko-vlozhennyih-podkomand-ispolzuya-python-argparse/29426523#29426523.
Кроме того, мне удалось найти решение, которое добавило псевдодействие для каждого параметра, а также решение, которое изменило форматер. Наконец, я представляю гибридное решение, которое добавляет псевдодействие для каждого параметра, но таким образом, что только форматировщик использует их, используя некоторые детали реализации.
Первое решение определяет настраиваемое действие, цель которого - вообще ничего не делать, но при этом выводит некоторую информацию об использовании. Различные варианты даны этому классу 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']
диктовать {'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
Следует отметить, что это единственный подход, который не печатает строку помощи для самих "ИНГРЕДИЕНТОВ", а только варианты.
Тем не менее, мне не нравится этот подход: он повторно реализует слишком много кода и полагается на слишком много внутренних деталей реализации 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()
метод.
Изменение 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
и поместите отформатированную таблицу в description
или же epilog
,
Другим вариантом является создание подкласса Action, который имитирует аспекты subparsers
учебный класс. Но это требует более глубокого понимания того, как этот класс Action обрабатывается при форматировании.
class _SubParsersAction(Action):
class _ChoicesPseudoAction(Action):
subparsers
Объект действия поддерживает список _choices_actions
этого PseudoAction
класс исключительно для того, чтобы обмануть средство форматирования справки для отображения подпарсеров, как если бы они были группой вложенных Actions
, Этот список не используется для анализа; только для справки форматирования.
Почему бы просто не разобрать аргументы самостоятельно, не используя 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
Затем вы можете делать все, что будет дальше со списком ингредиентов. Надеюсь, это поможет.