Подтвердить что ты не робот

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()

Мне нравится, что select = servers проверяет имена серверов во входном для меня, так что мне не придется. Однако, имея так много действительных вариантов, выход справки выглядит ужасно:

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.

Bonus:

  • Возможно ли иметь совпадение строк, нечувствительное к строкам для имен серверов?

Append

Я попытался использовать предложение michaelfilms, где параметры -o -s удалены из вышеприведенного вывода, и эта часть добавлена:

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

Я думаю, что это выглядит неплохо, но мне действительно нужно предоставить помощь для параметров -o и -s, поскольку пользователь не знал бы об этом в противном случае. Таким образом, я все еще не использую этот подход.

4b9b3361

Ответ 1

Я в основном повторяю то, что сказал Эрнест, - чтобы избежать уродливого длинного списка вариантов, установите metavar = '' для аргументов на основе выбора (хотя он не избавится от пробела между аргументом и запятой (например, -o , вместо -o,). Затем вы можете подробно описывать доступные варианты в общем описании (RawDescriptionHelpFormatter полезен здесь, если вы хотите, чтобы они были перечислены с явным отступом).

Я не понимаю, почему ответ Эрнеста был отклонен. Этот код

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()

выводит следующий вывод справки

usage: run.py [-h] [-l | -u] [-o] [-s]

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 , --only      Space separated list of case sensitive server names to
                   process. Allowed values are ApaServer, BananServer,
                   GulServer, SolServer, RymdServer, SkeppServer, HavsServer,
                   PiratServer, SvartServer, NattServer, SovServer
  -s , --skip      Space separated list of case sensitive server names to
                   exclude from processing. Allowed values are ApaServer,
                   BananServer, GulServer, SolServer, RymdServer, SkeppServer,
                   HavsServer, PiratServer, SvartServer, NattServer, SovServer

Это, надеюсь, то, что ищет оригинальный пост.

Ответ 2

Нет необходимости подклассифицировать что-либо. Просто передайте аргумент metavar с строкой, которую вы хотите отобразить в сообщении справки.

Подробнее см. документацию argparse.

Ответ 3

У меня есть эта же проблема, и в качестве обходного пути я использовал 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)

Ответ 4

Чтобы получить ожидаемый результат, вам потребуется подкласс argparse.HelpFormatter и реализовать нужное форматирование. В частности, вам нужно будет реализовать свой собственный метод _metavar_formatter, который отвечает за объединение всех вариантов в одну строку, разделенную запятыми.

Ответ 5

Почему не используется parser.add_argument_group, чтобы создать группу для ваших параметров на сервере и указать, что описание arg отображает список возможных вариантов? Затем передайте argparse.SUPPRESS в помощь для каждого из отдельных вариантов. Я считаю, что это даст вам то, что вы хотите.

Ответ 6

Это не поможет в ситуациях, когда список опций очень длинный, как в исходном вопросе, но для тех людей, которые, как и я, столкнулись с этим вопросом, ища способ сломать строки с умеренно длинными опциями в две строки, вот мое решение:

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

Ответ 7

http://bugs.python.org/issue16468 argparse only supports iterable choices - проблема, связанная с форматированием выбора. Список вариантов может отображаться в трех местах: строке использования, линиях справки и сообщениях об ошибках.

Все, что интересует парсер, делает тест in (__contains__). Но для форматирования, длинных списков, неограниченных "списков" (например, целых чисел > 100) и других объектов, которые не являются итерируемыми, возникают проблемы. metavar - это способ, которым текущие пользователи могут обойти большинство проблем форматирования (это может не помочь в сообщениях об ошибках). Посмотрите на эту проблему, чтобы получить представление о том, как изменить свою версию argparse.