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

Условные аргументы Python Argparse

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

p = argparse.ArgumentParser(description='...')
p.add_argument('--argument', required=False)
p.add_argument('-a', required=False) # only required if --argument is given
p.add_argument('-b', required=False) # only required if --argument is given

Из того, что я видел, другие люди, кажется, просто добавляют свою собственную проверку в конце:

if args.argument and (args.a is None or args.b is None):
    # raise argparse error here

Есть ли способ сделать это изначально внутри пакета argparse?

4b9b3361

Ответ 1

Я искал простой ответ на этот вопрос в течение некоторого времени. Все, что вам нужно сделать, это проверить, находится ли '--argument' в sys.argv, поэтому в основном для вашего образца кода, который вы могли бы просто сделать:

import argparse
import sys

if __name__ == '__main__':
    p = argparse.ArgumentParser(description='...')
    p.add_argument('--argument', required=False)
    p.add_argument('-a', required='--argument' in sys.argv) #only required if --argument is given
    p.add_argument('-b', required='--argument' in sys.argv) #only required if --argument is given
    args = p.parse_args()

Таким образом required получает либо True, либо False в зависимости от того, используется ли пользователь --argument. Уже протестировано, похоже, работает и гарантирует, что -a и -b имеют независимое поведение между собой.

Ответ 2

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

import argparse

class CondAction(argparse.Action):
    def __init__(self, option_strings, dest, nargs=None, **kwargs):
        x = kwargs.pop('to_be_required', [])
        super(CondAction, self).__init__(option_strings, dest, **kwargs)
        self.make_required = x

    def __call__(self, parser, namespace, values, option_string=None):
        for x in self.make_required:
            x.required = True
        try:
            return super(CondAction, self).__call__(parser, namespace, values, option_string)
        except NotImplementedError:
            pass

p = argparse.ArgumentParser()
x = p.add_argument("--a")
p.add_argument("--argument", action=CondAction, to_be_required=[x])

Точное определение CondAction будет зависеть от того, что именно следует делать --argument. Но, например, если --argument является регулярным типом действия take-one-argument-and-save-it, то достаточно наследовать от argparse._StoreAction должно быть достаточно.

В примере анализатора мы сохраняем ссылку на параметр --a внутри параметра --argument, и когда --argument отображается в командной строке, он устанавливает флаг required на --a на True. После того, как все параметры обработаны, argparse проверяет, что была выбрана любая опция, отмеченная как необходимая.

Ответ 3

Тест на анализ сообщений отлично, особенно если тестирование по умолчанию с is None соответствует вашим потребностям.

http://bugs.python.org/issue11588 'Add "necessarily inclusive" groups to argparse' рассматривает реализацию таких тестов, используя механизм groups (обобщение mutuall_exclusive_groups).

Я написал набор UsageGroups, который реализует тесты типа xor (взаимоисключающие), and, or и not. Я думал, что те, где они полны, но я не смог выразить ваше дело с точки зрения этих операций. (похоже, мне нужно nand - нет и, см. ниже)

Этот script использует пользовательский класс Test, который по существу реализует ваш тест после анализа. seen_actions - это список действий, которые видел синтаксический анализ.

class Test(argparse.UsageGroup):
    def _add_test(self):
        self.usage = '(if --argument then -a and -b are required)'
        def testfn(parser, seen_actions, *vargs, **kwargs):
            "custom error"
            actions = self._group_actions
            if actions[0] in seen_actions:
                if actions[1] not in seen_actions or actions[2] not in seen_actions:
                    msg = '%s - 2nd and 3rd required with 1st'
                    self.raise_error(parser, msg)
            return True
        self.testfn = testfn
        self.dest = 'Test'
p = argparse.ArgumentParser(formatter_class=argparse.UsageGroupHelpFormatter)
g1 = p.add_usage_group(kind=Test)
g1.add_argument('--argument')
g1.add_argument('-a')
g1.add_argument('-b')
print(p.parse_args())

Образец вывода:

1646:~/mypy/argdev/usage_groups$ python3 issue25626109.py --arg=1 -a1
usage: issue25626109.py [-h] [--argument ARGUMENT] [-a A] [-b B]
                        (if --argument then -a and -b are required)
issue25626109.py: error: group Test: argument, a, b - 2nd and 3rd required with 1st

usage и сообщения об ошибках по-прежнему нуждаются в работе. И это не делает ничего такого, что тест после разбора не может.


Ваш тест вызывает ошибку, если (argument & (!a or !b)). И наоборот, разрешено !(argument & (!a or !b)) = !(argument & !(a and b)). Добавив тест nand в мои классы UsageGroup, я могу реализовать ваш случай как:

p = argparse.ArgumentParser(formatter_class=argparse.UsageGroupHelpFormatter)
g1 = p.add_usage_group(kind='nand', dest='nand1')
arg = g1.add_argument('--arg', metavar='C')
g11 = g1.add_usage_group(kind='nand', dest='nand2')
g11.add_argument('-a')
g11.add_argument('-b')

Использование (с помощью !() для отметки теста 'nand'):

usage: issue25626109.py [-h] !(--arg C & !(-a A & -b B))

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


В моих тестах входы, которые успешно выполняются,:

''
'-a1'
'-a1 -b2'
'--arg=3 -a1 -b2'

Предполагается, что сообщения, вызывающие ошибки, следующие:

'--arg=3'
'--arg=3 -a1'
'--arg=3 -b2'

Ответ 4

До тех пор, пока http://bugs.python.org/issue11588 не будет решена, я просто использую nargs:

p = argparse.ArgumentParser(description='...')
p.add_argument('--arguments', required=False, nargs=2, metavar=('A', 'B'))

Таким образом, если кто-то поставляет --arguments, он будет иметь 2 значения.

Возможно, его результат CLI менее читабельен, но код намного меньше. Вы можете исправить это с помощью хорошего документа/справки.

Ответ 5

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

Например, если --option foo то требуются также некоторые аргументы, которые не требуются, если --option bar:

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('--option', required=True,
        help='foo and bar need different args')

    if 'foo' in sys.argv:
        parser.add_argument('--foo_opt1', required=True,
           help='--option foo requires "--foo_opt1"')
        parser.add_argument('--foo_opt2', required=True,
           help='--option foo requires "--foo_opt2"')
        ...

    if 'bar' in sys.argv:
        parser.add_argument('--bar_opt', required=True,
           help='--option bar requires "--bar_opt"')
        ...

Он не идеален - например, proggy --option foo --foo_opt1 bar неоднозначна, но для того, что мне нужно было сделать, все в порядке.

Ответ 6

Для аргументов я придумал быстрое n-грязное решение вроде этого. Предположения: (1) "-help" должен отображать справку и не жаловаться на требуемый аргумент, и (2) мы анализируем sys.argv

p = argparse.ArgumentParser(...)
p.add_argument('-required', ..., required = '--help' not in sys.argv )

Это можно легко изменить в соответствии с конкретными настройками. Для требуемых позиционных элементов (которые не будут запрашиваться, если в командной строке указано "-help" ), я пришел к следующему: [positionals не позволяют использовать required=... keyword arg!]

p.add_argument('pattern', ..., narg = '+' if '--help' not in sys.argv else '*' )

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