Python argparse wzajemnie wykluczająca się grupa

88

Potrzebuję:

pro [-a xxx | [-b yyy -c zzz]]

Próbowałem tego, ale nie działa. Czy ktoś mógłby mi pomóc?

group= parser.add_argument_group('Model 2')
group_ex = group.add_mutually_exclusive_group()
group_ex.add_argument("-a", type=str, action = "store", default = "", help="test")
group_ex_2 = group_ex.add_argument_group("option 2")
group_ex_2.add_argument("-b", type=str, action = "store", default = "", help="test")
group_ex_2.add_argument("-c", type=str, action = "store", default = "", help="test")

Dzięki!

Sean
źródło
Podłączam, ale chciałem wspomnieć o mojej bibliotece Joffrey . Pozwala na zrobienie tego, czego chce to pytanie, na przykład bez używania podpoleceń (jak w zaakceptowanej odpowiedzi) lub samodzielnego sprawdzania wszystkiego (jak w odpowiedzi z drugą najwyższą liczbą głosów).
witaj

Odpowiedzi:

109

add_mutually_exclusive_groupnie powoduje wykluczenia całej grupy. To sprawia, że ​​opcje w ramach grupy wzajemnie się wykluczają.

To, czego szukasz, to podkomendy . Zamiast prog [-a xxxx | [-b yyy -c zzz]], miałbyś:

prog 
  command 1 
    -a: ...
  command 2
    -b: ...
    -c: ...

Aby wywołać z pierwszym zestawem argumentów:

prog command_1 -a xxxx

Aby wywołać z drugim zestawem argumentów:

prog command_2 -b yyyy -c zzzz

Możesz również ustawić argumenty polecenia podrzędnego jako pozycyjne.

prog command_1 xxxx

Coś jak git lub svn:

git commit -am
git merge develop

Przykład roboczy

# create the top-level parser
parser = argparse.ArgumentParser(prog='PROG')
parser.add_argument('--foo', action='store_true', help='help for foo arg.')
subparsers = parser.add_subparsers(help='help for subcommand')

# create the parser for the "command_1" command
parser_a = subparsers.add_parser('command_1', help='command_1 help')
parser_a.add_argument('a', type=str, help='help for bar, positional')

# create the parser for the "command_2" command
parser_b = subparsers.add_parser('command_2', help='help for command_2')
parser_b.add_argument('-b', type=str, help='help for b')
parser_b.add_argument('-c', type=str, action='store', default='', help='test')

Sprawdź to

>>> parser.print_help()
usage: PROG [-h] [--foo] {command_1,command_2} ...

positional arguments:
  {command_1,command_2}
                        help for subcommand
    command_1           command_1 help
    command_2           help for command_2

optional arguments:
  -h, --help            show this help message and exit
  --foo                 help for foo arg.
>>>

>>> parser.parse_args(['command_1', 'working'])
Namespace(a='working', foo=False)
>>> parser.parse_args(['command_1', 'wellness', '-b x'])
usage: PROG [-h] [--foo] {command_1,command_2} ...
PROG: error: unrecognized arguments: -b x

Powodzenia.

Jonathan
źródło
Umieściłem je już w grupie argumentów. Jak w takim przypadku dodać polecenie podrzędne? Dzięki!
Sean
1
Zaktualizowano przykładowym kodem. Nie będziesz używać grup, ale subparserów.
Jonathan
6
Ale jak byś zrobił to, o co pierwotnie pytał OP? Obecnie mam zestaw poleceń podrzędnych, ale jedno z tych poleceń podrzędnych wymaga możliwości wyboru między[[-a <val>] | [-b <val1> -c <val2>]]
code_dredd
2
To nie odpowiada na pytanie, ponieważ nie pozwala na wydawanie poleceń „bez nazwy” i osiąganie tego, o co prosił OP[-a xxx | [-b yyy -c zzz]]
Ojciec chrzestny
34

Chociaż odpowiedź Jonathana jest idealna dla złożonych opcji, istnieje bardzo proste rozwiązanie, które będzie działać w prostych przypadkach, np. 1 opcja wyklucza 2 inne opcje, takie jak w

command [- a xxx | [ -b yyy | -c zzz ]] 

lub nawet jak w pierwotnym pytaniu:

pro [-a xxx | [-b yyy -c zzz]]

Oto jak bym to zrobił:

parser = argparse.ArgumentParser()

# group 1 
parser.add_argument("-q", "--query", help="query", required=False)
parser.add_argument("-f", "--fields", help="field names", required=False)

# group 2 
parser.add_argument("-a", "--aggregation", help="aggregation",
                    required=False)

Używam tutaj opcji przekazanych do opakowania wiersza poleceń do odpytywania mongodb. collectionInstancja może albo wywołać metodę aggregatelub metody findz opcjonalnych argumentów queryi fieldsstąd widać dlaczego pierwsze dwa argumenty są kompatybilne i ostatni nie jest.

Więc teraz biegnę parser.parse_args()i sprawdzam zawartość:

args = parser().parse_args()

print args.aggregation
if args.aggregation and (args.query or args.fields):
    print "-a and -q|-f are mutually exclusive ..."
    sys.exit(2)

Oczywiście ten mały hack działa tylko w prostych przypadkach, a sprawdzenie wszystkich możliwych opcji byłoby koszmarem, jeśli masz wiele wzajemnie wykluczających się opcji i grup. W takim przypadku powinieneś przełamać swoje opcje w grupach dowodzenia, jak sugerował Jonathan.

Oz123
źródło
5
Nie nazwałbym tego „hackem” w tym przypadku, ponieważ wydaje się bardziej czytelny i łatwiejszy w zarządzaniu - dziękuję za wskazanie!
sage
13
Jeszcze lepszym sposobem byłoby użycie parser.error("-a and -q ..."). W ten sposób pełna pomoc dotycząca użytkowania zostanie wydrukowana automatycznie.
WGH
Należy pamiętać, że w tym przypadku należy również zweryfikować przypadki, takie jak: (1) obie qi fsą wymagane w pierwszej grupie to użytkownik, (2) każda z grup jest wymagana. I to sprawia, że ​​„proste” rozwiązanie nie jest już takie proste. Zgadzam się więc, że to bardziej hack dla ręcznie wykonanego scenariusza, ale nie jest to prawdziwe rozwiązanie
Ojciec chrzestny
4

Jest łatka Pythona (w fazie rozwoju), która pozwoli ci to zrobić.
http://bugs.python.org/issue10984

Chodzi o to, aby umożliwić nakładanie się wzajemnie wykluczających się grup. Więc usagemoże wyglądać tak:

pro [-a xxx | -b yyy] [-a xxx | -c zzz]

Zmiana kodu argparse, aby można było utworzyć dwie takie grupy, była łatwa. Zmiana usagekodu formatowania wymagała napisania niestandardowego HelpFormatter.

W programie argparsegrupy akcji nie mają wpływu na analizowanie. Są tylko helpnarzędziem do formatowania. W grupie helpwzajemnie wykluczające się grupy wpływają tylko na usagelinię. Podczas analizowania parserużywa wzajemnie wykluczających się grup do tworzenia słownika potencjalnych konfliktów ( anie może wystąpić z blub c, bnie może wystąpić z aitp.), A następnie zgłasza błąd w przypadku wystąpienia konfliktu.

Myślę, że bez tej argparse patch najlepiej przetestować utworzoną przez parse_argssiebie przestrzeń nazw (np. Jeśli obie ai bmają wartości niedomyślne) i zgłosić swój własny błąd. Możesz nawet użyć własnego mechanizmu błędu parsera.

parser.error('custom error message')
hpaulj
źródło
1
Problem z Pythonem: bugs.python.org/issue11588 bada sposoby pisania niestandardowych testów wyłącznych / włączających.
hpaulj