Argparse: игнорировать множественные позиционные аргументы при указании необязательного аргумента

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

В основном я пытаюсь реплицировать поведение -help: когда вы указываете -h, все отсутствующие необходимые аргументы игнорируются.

Пример кода:

parser = argparse.ArgumentParser(description="Foo bar baz")
parser.add_argument('arg1', help='arg1 is a positional argument that does this')
parser.add_argument('arg2', help='arg2 is a positional argument that does this')
parser.add_argument('-l', '--list', dest='list', help='this is an optional argument that prints stuff')

options, args = parser.parse_args()

if options.list:
   print "I list stuff"

И, конечно, если я запустил его сейчас, я получаю:

error: too few arguments

Я пробовал разные вещи, например nargs='?', но ничего не мог заставить работать.

Этот вопрос очень похож, но на него не ответил.

Любая помощь оценивается,

Спасибо,

Ответы

Ответ 1

К сожалению, argparse недостаточно для этого достаточно гибким. Лучшее, что вы можете сделать, это сделать arg1 и arg2 необязательным с помощью nargs="?" и проверить, предоставлены ли они, если это необходимо.

Внутреннее действие help реализуется путем печати справочного сообщения и выхода из программы, как только -h или --help встречаются в командной строке. Вы можете написать подобное действие самостоятельно, что-то вроде

class MyAction(argparse.Action):
    def __call__(self, parser, values, namespace, option_string):
        print "Whatever"
        parser.exit()

(Предупреждение: непроверенный код!)

Тем не менее, существуют определенные недостатки последнего подхода. Сообщение справки безоговорочно показывает arg1 и arg2 как обязательные аргументы. И синтаксический анализ командной строки просто прекращается, когда встречается -l или --list, игнорируя любые дополнительные аргументы. Это поведение вполне приемлемо для --help, но оно меньше желаемого для других параметров.

Ответ 2

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

В данном примере я бы использовал следующее:

parser = argparse.ArgumentParser(description="Foo bar baz")
subparsers = parser.add_subparsers(description='available subcommands')

parser_main = subparsers.add_parser('<main_command_name>')
parser_main.add_argument('arg1', help='arg1 is a positional argument that does this')
parser_main.add_argument('arg2', help='arg2 is a positional argument that does this')

parser_list = subparsers.add_parser('list', help='this is a subcommand that prints stuff')

options, args = parser.parse_args()

Я забыл некоторые детали, которые вы можете включить (например, set_defaults(func=list)), которые упоминаются в документации argparse.

Ответ 3

Я думаю, что nargs='*' полезен.

Позиционные аргументы игнорируются, тогда вы можете использовать if для проверки позиционных аргументов как true или false.

http://docs.python.org/library/argparse.html#nargs

Ответ 4

Возможно, я нашел решение. Правда, это грязный хак, но он работает.

Примечание. Все перечисленные ниже применимы к Python 3.3.2.

В соответствии с ответом здесь, parse_args проверяет, какие действия требуются, и выдает ошибку, если какой-либо из них отсутствует. Я предлагаю переопределить это поведение.

При подклассификации ArgumentParser мы можем определить новый метод ArgumentParser.error (оригинальный здесь), который будет проверять, была ли ошибка выбрана, потому что некоторые аргументы отсутствуют и предпринимаются необходимые действия. Код следует:

import argparse
import sys
from gettext import gettext as _

class ArgumentParser(argparse.ArgumentParser):
    skip_list = []

    def error(self, message):
        # Let see if we are missing arguments
        if message.startswith('the following arguments are required:'):
            missingArgs = message.split('required: ')[1].split(', ')
            newArgs = []    # Creating a list of whatever we should not skip but is missing
            for arg in missingArgs:
                if arg not in self.skip_list:
                    newArgs.append(arg)
                else:
                    self.skip_list.remove(arg)  # No need for it anymore
            if len(newArgs) == 0:
                return  # WARNING! UNTESTED! MAY LEAD TO SPACETIME MELTDOWN!
            else:   # Some required stuff is still missing, so we show a corrected error message
                message = _('the following arguments are required: %s') % ', '.join(newArgs)

        self.print_usage(sys.stderr)    # Original method behavior
        args = {'prog': self.prog, 'message': message}
        self.exit(2, _('%(prog)s: error: %(message)s\n') % args)

Новый метод сначала проверяет, является ли ошибка, потому что в командной строке отсутствуют аргументы (см. здесь для кода, который генерирует ошибку). Если это так, метод получает имена аргументов из сообщения об ошибке и помещает их в список (missingArgs).

Затем мы перебираем этот список и проверяем, какие аргументы должны быть пропущены, и которые по-прежнему необходимы. Чтобы определить, какие аргументы пропустить, мы сравниваем их с skip_list. Это поле в нашем подклассе ArgumentParser, которое должно содержать имена аргументов, которые нужно пропустить, даже если они необходимы парсеру. Обратите внимание, что аргументы, которые находятся в skip_list, удаляются из него, когда они найдены.

Если все еще требуются аргументы, отсутствующие в командной строке, метод выдает исправленное сообщение об ошибке. Однако, если все отсутствующие аргументы должны быть пропущены, метод возвращает.

ПРЕДУПРЕЖДЕНИЕ! Исходное определение ArgumentParser.error гласит, что если он переопределен в подклассе , он не должен возвращать, а скорее должен выйти или создать исключение. Таким образом, то, что показано здесь, потенциально опасно и может привести к сбою вашей программы, вашему компьютеру загореться или хуже - ЭТО МОЖЕТ ИСПРАВИТЬ ВСЕ ВАШЕ ЧАЙ. Однако, похоже, в этом конкретном случае (отсутствующие требуемые аргументы) можно безопасно вернуться из метода. Но может и не быть. Вы были предупреждены.

Чтобы заполнить skip_list, мы могли бы использовать такой код:

class SpecialHelp(argparse._HelpAction):
    def __call__(self, parser, namespace, values, option_string=None):
        parser.print_help()
        print()
        for action in parser._actions:
            if action != self and action.required:
                parser.skip_list.append(argparse._get_action_name(action))

Этот конкретный класс имитирует встроенное действие help, но вместо его выхода он вставляет все оставшиеся необходимые аргументы в skip_list.

Надеюсь, мой ответ поможет и удачи.

Ответ 5

Это может быть уродливо, но это то, что я обычно делаю.

def print_list():
    the_list = ["name1", "name2"]
    return "{0}".format(the_list)

...
parser.add_argument("-l", "--list", action='version',
                    version=print_list(), help="print the list")