Argparse: некоторые взаимоисключающие аргументы в требуемой группе

У меня есть набор аргументов, которые можно логически разделить в 2 группы:

  • Действия: A1, A2, A3 и т.д.
  • Информация: I1, I2, I3 и т.д.

Для запуска программы требуется хотя бы один из этих аргументов, но аргументы "информация" могут использоваться с аргументами "action". Так

  • Требуется хотя бы одно из действий или информации
  • Все действия являются взаимоисключающими.

Я не могу найти, как это сделать, используя argparse. Я знаю о add_mutually_exclusive_group и его аргументе required, но я не могу использовать его в "Действиях", потому что это фактически не требуется. Конечно, я могу добавить условие после argparse, чтобы вручную проверить мои правила, но это похоже на взломать. Может ли argparse сделать это?

Изменить: Извините, вот несколько примеров.

# Should pass
--A1
--I1
--A1 --I2
--A2 --I1 --I2

# Shouldn't pass
--A1 --A2
--A1 --A2 --I1

Ответ 1

Там нет ничего плохого в проверке аргументов после того, как они были проанализированы. Просто соберите их все в одном наборе, затем подтвердите, что он не пуст и содержит не более одного действия.

actions = {"a1", "a2", "a3"}
informations = {"i1", "i2", "i3"}
p = argparse.ArgumentParser()
# Contents of actions and informations contrived
# to make the example short. You may need a series
# of calls to add_argument to define the options and
# constants properly
for ai in actions + informations:
    p.add_argument("--" + ai, action='append_const', const=ai, dest=infoactions)

args = p.parse_args()
if not args.infoactions:
    p.error("At least one action or information required")
elif len(actions.intersection(args.infoactions)) > 1:
    p.error("At most one action allowed")

Ответ 2

Мне что-то не хватает или вы просто хотите:

import argparse
import os

def main():
    parser = argparse.ArgumentParser()
    actions = parser.add_mutually_exclusive_group()
    actions.add_argument("-A1", action="store_true")
    actions.add_argument("-A2", action="store_true")
    actions.add_argument("-A3", action="store_true")
    low = int(os.environ.get('LOWER_BOUNDS', 0))
    high = int(os.environ.get('UPPER_BOUNDS', 3)) + 1
    infos = parser.add_argument_group()
    for x in range(low, high):
        infos.add_argument("-I" + str(x), action="store_true")

    args = parser.parse_args()
    if not any(vars(args).values()):
        parser.error('No arguments provided.')
    print args

if __name__ == '__main__':
    main()

выход:

$ python test.py 
usage: test.py [-h] [-A1 | -A2 | -A3] [-I0] [-I1] [-I2] [-I3]
test.py: error: No arguments provided.
$ python test.py -A1
Namespace(A1=True, A2=False, A3=False, I1=False, I2=False, I3=False)
$ python test.py -A1 -A2
usage: test.py [-h] [-A1 | -A2 | -A3] [-I1] [-I2] [-I3]
test.py: error: argument -A2: not allowed with argument -A1
$ python test.py -A1 -I1
Namespace(A1=True, A2=False, A3=False, I1=True, I2=False, I3=False)
$ python test.py -A1 -I1 -I2
Namespace(A1=True, A2=False, A3=False, I1=True, I2=True, I3=False)
$ python test.py -A1 -I1 -I2 -I3
Namespace(A1=True, A2=False, A3=False, I1=True, I2=True, I3=True)
$ UPPER_BOUNDS=40 python test.py -A1 -I1 -I2 -I40
Namespace(A1=True, A2=False, A3=False, I0=False, I1=True, I10=False, I11=False, I12=False, I13=False, I14=False, I15=False, I16=False, I17=False, I18=False, I19=False, I2=True, I20=False, I21=False, I22=False, I23=False, I24=False, I25=False, I26=False, I27=False, I28=False, I29=False, I3=False, I30=False, I31=False, I32=False, I33=False, I34=False, I35=False, I36=False, I37=False, I38=False, I39=False, I4=False, I40=True, I5=False, I6=False, I7=False, I8=False, I9=False)

PS. Я действительно не предлагаю этот "неограниченный" подход -I#.. но вот пример этого.

Ответ 3

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

Я работал над патчем, чтобы обеспечить более сложную логику и вложенные группы. Логика тестирования не так уж плоха, но разработка хорошего пользовательского интерфейса сложна, так как создает значимую строку usage. Таким образом, улучшение, вероятно, никогда не увидит производство.

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