Argparse: игнорировать несколько позиционных аргументов, если указан необязательный аргумент - PullRequest
18 голосов
/ 20 сентября 2011

Я пытаюсь заставить 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='?', но ничего не получалось.

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

Ответы [ 6 ]

8 голосов
/ 20 сентября 2011

К сожалению, 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, но менее желательно для других вариантов.

6 голосов
/ 22 октября 2011

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

Для вашего данного примера я бы использовал что-то вроде следующего:

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 документации .

1 голос
/ 19 августа 2013

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

Примечание: все следующее относится к 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's 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.

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

1 голос
/ 20 мая 2012

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

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

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

0 голосов
/ 17 декабря 2018

Самый чистый подход, который мне удалось найти, - это разбить синтаксический анализ на два этапа. Первая проверка для -l/--list:

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

options, remainder = parser.parse_known_args()

Теперь, так как вы использовали parse_known_args, вы не получите здесь сообщения об ошибке, и вы можете решить, что делать с remainder аргументов:

if options.list:
    print "I list stuff"
else:
    parser = argparse.ArgumentParser(add_help=False)
    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')
    options = parser.parse_args(remainder)

Возможно, вы захотите установить опцию использования в первом парсере, чтобы строка справки стала немного лучше.

0 голосов
/ 10 января 2014

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

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")
...