Как создать взаимоисключающую группу argparse с несколькими позиционными параметрами?

Я пытаюсь проанализировать аргументы командной строки, так что возможны три возможности ниже:

script
script file1 file2 file3 …
script -p pattern

Таким образом, список файлов является необязательным. Если задана опция -p pattern, в командной строке больше ничего не может быть. Указанный в формате "использование", вероятно, будет выглядеть следующим образом:

script [-p pattern | file [file …]]

Я думал, что способ сделать это с помощью модуля Python argparse будет таким:

parser = argparse.ArgumentParser(prog=base)
group = parser.add_mutually_exclusive_group()
group.add_argument('-p', '--pattern', help="Operate on files that match the glob pattern")
group.add_argument('files', nargs="*", help="files to operate on")
args = parser.parse_args()

Но Python жалуется, что мой позиционный аргумент должен быть необязательным:

Traceback (most recent call last):
  File "script", line 92, in <module>
    group.add_argument('files', nargs="*", help="files to operate on")
…
ValueError: mutually exclusive arguments must be optional

Но в документации argparse говорится, что аргумент "*" для nargs означает, что он является необязательным.

Я не смог найти другое значение для nargs, которое тоже делает трюк. Самое близкое, что я пришел, использует nargs="?", но это только захватывает один файл, а не необязательный список любого числа.

Можно ли написать синтаксис такого типа с помощью argparse?

Ответ 1

короткий ответ

Добавьте default в * positional

долго

Код, который вызывает ошибку,

    if action.required:
        msg = _('mutually exclusive arguments must be optional')
        raise ValueError(msg)

Если я добавлю * в синтаксический анализатор, я вижу, что атрибут required установлен:

In [396]: a=p.add_argument('bar',nargs='*')
In [397]: a
Out[397]: _StoreAction(option_strings=[], dest='bar', nargs='*', const=None, default=None, type=None, choices=None, help=None, metavar=None)
In [398]: a.required
Out[398]: True

тогда как для a ? это будет False. Я буду копать немного дальше в коде, чтобы понять, почему разница. Это может быть ошибка или пропущенная "функция", или может быть веская причина. Трудная вещь с "необязательными" позициями заключается в том, что ответ "нет" - это ответ, т.е. Допустимый пустой список значений.

In [399]: args=p.parse_args([])
In [400]: args
Out[400]: Namespace(bar=[], ....)

Таким образом, у mutally_exclusive должен быть какой-то способ отличить по умолчанию [] и real [].

Теперь я бы предложил использовать --files, помеченный аргумент, а не позиционный, если вы ожидаете, что argparse выполнит взаимоисключающее тестирование.


Код, устанавливающий атрибут required позиционного элемента:

    # mark positional arguments as required if at least one is
    # always required
    if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]:
        kwargs['required'] = True
    if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs:
        kwargs['required'] = True

Таким образом, решение должно указывать значение по умолчанию для *

In [401]: p=argparse.ArgumentParser()
In [402]: g=p.add_mutually_exclusive_group()
In [403]: g.add_argument('--foo')
Out[403]: _StoreAction(option_strings=['--foo'], dest='foo', nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None)
In [404]: g.add_argument('files',nargs='*',default=None)
Out[404]: _StoreAction(option_strings=[], dest='files', nargs='*', const=None, default=None, type=None, choices=None, help=None, metavar=None)
In [405]: p.parse_args([])
Out[405]: Namespace(files=[], foo=None)

По умолчанию может быть даже []. Синтаксический анализатор может различать предоставленный вами по умолчанию и тот, который он использует, если он не указан.

oops - default=None был неправильным. Он передает тест add_argument и required, но создает взаимно-исключающую ошибку. Подробности заключаются в том, как код различает установленные пользователем значения по умолчанию и автоматические. Поэтому используйте ничего, кроме None.

Я ничего не вижу в документации по этому поводу. Я должен проверить ошибку/проблемы, чтобы увидеть, что тема обсуждалась. Вероятно, он тоже появился на SO.

Ответ 2

Вы пытаетесь использовать аргумент "файлы", чтобы поймать несколько файлов, но вы не поставляете его в примерах cmdline. Я думаю, что библиотека запуталась в том, что вы не используете префикс тире. Я бы предложил следующее:

import argparse

parser = argparse.ArgumentParser(prog="base")
group = parser.add_mutually_exclusive_group()
group.add_argument('-p', '--pattern', action="store", help="Operate on files that match the glob pattern")
group.add_argument('-f', '--files',  nargs="*", action="store", help="files to operate on")

args = parser.parse_args()
print args.pattern
print args.files

Ответ 3

    import argparse
    parse  = argparse.ArgumentParser()
    parse.add_argument("-p",'--pattern',help="Operates on File")
    parse.add_argument("files",nargs = "*",help="Files to operate on")

    arglist = parse.parse_args(["-p","pattern"])
    print arglist
    arglist = parse.parse_args()
    print arglist
    arglist = parse.parse_args(["file1","file2","file3"])
    print arglist