Расширьте argparse, чтобы записать имена наборов в тексте справки для необязательного выбора аргументов и определить эти наборы один раз в конце - PullRequest
12 голосов
/ 14 марта 2012

Пример проблемы

Если у меня есть список допустимых строк параметров, которые совместно используются несколькими аргументами, список записывается в нескольких местах в строке справки. Трудно читать:

def main():
    elements = ['a', 'b', 'c', 'd', 'e', 'f']

    parser = argparse.ArgumentParser()
    parser.add_argument(
        '-i',
        nargs='*',
        choices=elements,
        default=elements,
        help='Space separated list of case sensitive element names.')
    parser.add_argument(
        '-e',
        nargs='*',
        choices=elements,
        default=[],
        help='Space separated list of case sensitive element names to '
        'exclude from processing')

    parser.parse_args()

При запуске вышеуказанной функции с аргументом командной строки --help она показывает:

usage: arguments.py [-h] [-i [{a,b,c,d,e,f} [{a,b,c,d,e,f} ...]]]
                    [-e [{a,b,c,d,e,f} [{a,b,c,d,e,f} ...]]]

optional arguments:
  -h, --help            show this help message and exit
  -i [{a,b,c,d,e,f} [{a,b,c,d,e,f} ...]]
                        Space separated list of case sensitive element names.
  -e [{a,b,c,d,e,f} [{a,b,c,d,e,f} ...]]
                        Space separated list of case sensitive element names
                        to exclude from processing

Что бы хорошо

Было бы неплохо, если бы можно было определить имя списка опций и в выводе справки написать имя списка опций в нескольких местах и ​​определить его последним из всех. Теоретически это будет работать так:

def main_optionlist():
    elements = ['a', 'b', 'c', 'd', 'e', 'f']

    # Two instances of OptionList are equal if and only if they
    # have the same name (ALFA in this case)

    ol = OptionList('ALFA', elements)

    parser = argparse.ArgumentParser()
    parser.add_argument(
        '-i',
        nargs='*',
        choices=ol,
        default=ol,
        help='Space separated list of case sensitive element names.')
    parser.add_argument(
        '-e',
        nargs='*',
        choices=ol,
        default=[],
        help='Space separated list of case sensitive element names to '
        'exclude from processing')

    parser.parse_args()

И при запуске вышеупомянутой функции с аргументом командной строки --help она будет показывать нечто похожее на:

usage: arguments.py [-h] [-i [ALFA [ALFA ...]]]
                    [-e [ALFA [ALFA ...]]]

optional arguments:
  -h, --help            show this help message and exit
  -i [ALFA [ALFA ...]]
                        Space separated list of case sensitive element names.
  -e [ALFA [ALFA ...]]
                        Space separated list of case sensitive element names
                        to exclude from processing
sets in optional arguments:
  ALFA                  {a,b,c,d,e,f}

Вопрос

Мне нужно:

  • Замените {'l', 'i', 's', 't', 's'}, показанные с именем опции, в необязательных аргументах.
  • В конце текста справки показывают раздел, поясняющий, из каких элементов состоит каждое имя опции.

Итак, я спрашиваю:

  1. Возможно ли это с помощью argparse?
  2. Какие классы мне нужно наследовать и какие методы мне нужно переопределить?

Я попытался посмотреть на источник argparse, но так как эта модификация выглядит довольно продвинутой, я не знаю, как начать.

Ответы [ 3 ]

18 голосов
/ 24 марта 2012

Мой ответ вообще не пытается расширить argparse, а использует доступные опции argparse как есть ... Это решает вашу ситуацию?

import argparse
import textwrap

def main():
    elements = ['a', 'b', 'c', 'd', 'e', 'f']

    parser = argparse.ArgumentParser(
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog = textwrap.dedent('''\
            sets in optional arguments: 
                ALFA\t\t{a,b,c,d,e,f}"
            '''))

    parser.add_argument(
        '-i',
        nargs='*',
        choices=elements,
        default=elements,
        metavar="ALFA",
        help='Space separated list of case sensitive element names.')
    parser.add_argument(
        '-e',
        nargs='*',
        choices=elements,
        default=[],
        metavar="ALFA",
        help='Space separated list of case sensitive element names to '
        'exclude from processing')

    parser.parse_args()

Вывод

usage: test.py [-h] [-i [ALFA [ALFA ...]]] [-e [ALFA [ALFA ...]]]

optional arguments:
  -h, --help            show this help message and exit
  -i [ALFA [ALFA ...]]  Space separated list of case sensitive element names.
  -e [ALFA [ALFA ...]]  Space separated list of case sensitive element names
                        to exclude from processing

sets in optional arguments: 
    ALFA                {a,b,c,d,e,f}"

Приятной особенностью этого подхода является то, что вы можете свободно называть метавар по своему желанию для каждого флага, и вы можете вручную отформатировать эпилог, чтобы отразить также любое описание формата.Подклассы не нужны.

10 голосов
/ 27 марта 2012

Полностью универсальное решение в соответствии с запросом удаленного теперь донора и в отличие от других ответов:

import argparse
from operator import itemgetter

class OptionListGroup(object):
  class GroupAction(object):
    def __init__(self, left, right):
      self.help = right
      self.option_strings = [left]
      self.nargs = 0

  def __init__(self, lists):
    self.description = None
    self.title = "referenced sets"
    self._group_actions = [self.GroupAction(name, self.format_list(lst))
                           for name, lst in sorted(lists)]

  def format_list(self, lst):
    return '{%s}' % ', '.join(map(str, lst))

class MyArgParser(argparse.ArgumentParser):
  def __init__(self, *args, **kwargs):
    self._option_lists = {}
    super(MyArgParser, self).__init__(*args, **kwargs)

  def parse_args(self, *args, **kw):
    self._action_groups.append(OptionListGroup(self._option_lists.values()))
    return super(MyArgParser, self).parse_args(*args, **kw)

  def add_option_list(self, name, lst):
    if name in map(itemgetter(0), self._option_lists.values()):
      raise ValueError, "Name already existing"
    self._option_lists[id(lst)] = (name, lst)

  def add_argument(self, *args, **kw):
    name_list = self._option_lists.get(id(kw.get('choices')))
    if name_list:
      kw['metavar'] = name_list[0]
    return super(MyArgParser, self).add_argument(*args, **kw)

Пример использования:

alfa = ['a', 'b', 'c', 'd', 'e', 'f']
num = [1, 2, 3]

parser = MyArgParser()

parser.add_option_list('ALFA', alfa)
parser.add_option_list('NUM', num)

parser.add_argument(
  '-a',
  nargs='*',
  choices=alfa,
  default=alfa,
  help='Characters (defaults to include all)')

parser.add_argument(
  '-e',
  nargs='*',
  choices=num,
  default=[],
  help='Digits (defaults to exclude all)')

parser.parse_args()

Вывод справки:

usage: argparse-optionlist.py [-h] [-a [ALFA [ALFA ...]]]
                              [-e [NUM [NUM ...]]]

optional arguments:
  -h, --help            show this help message and exit
  -a [ALFA [ALFA ...]]  Characters (defaults to include all)
  -e [NUM [NUM ...]]    Digits (defaults to exclude all)

referenced sets:
  ALFA                  {a, b, c, d, e, f}
  NUM                   {1, 2, 3}
1 голос
/ 24 марта 2012

Я не совсем уверен, что понимаю, чего вы хотите.,. Как насчет чего-то похожего на следующее:

import argparse

elements = ['a', 'b', 'c', 'd', 'e', 'f']

class myparser(argparse.ArgumentParser):
    def add_argument(self,*args,**kwargs):
        choice=kwargs.get('choices',None)
        if(choice is elements):
            kwargs['metavar']='ALFA'

        return argparse.ArgumentParser.add_argument(self,*args,**kwargs)

def main():

    epilog="""sets in optional arguments:
  ALFA                  %s"""%(str(elements).replace('[','{').replace(']','}'))

    parser = myparser(epilog=epilog,formatter_class=argparse.RawTextHelpFormatter) 
    parser.add_argument(
        '-i',
        nargs='*',
        choices=elements,
        default=elements,
        help='Space separated list of case sensitive element names.')
    parser.add_argument(
        '-e',
        nargs='*',
        choices=elements,
        default=[],
        help='Space separated list of case sensitive element names to '
        'exclude from processing')

    parser.parse_args()


main()

При желании вы можете просто использовать ключевое слово metavar напрямую.Изменение форматирования - заставить эпилог выглядеть так, как вы хотите.Как правило, я бы посоветовал не использовать его.

РЕДАКТИРОВАТЬ

В зависимости от версии Python у вас может быть доступно RawDescriptionHelpFormatter, которое я бы посоветовал использовать для этой проблемы.1012 *

...