Rozwiązanie dostarczone przez @Vikas nie działa w przypadku opcjonalnych argumentów specyficznych dla podkomendy, ale podejście jest poprawne. Oto ulepszona wersja:
import argparse
parser = argparse.ArgumentParser(prog='PROG')
parser.add_argument('--foo', action='store_true', help='foo help')
subparsers = parser.add_subparsers(help='sub-command help', dest='subparser_name')
parser_a = subparsers.add_parser('command_a', help='command_a help')
parser_a.add_argument('bar', type=int, help='bar help')
parser_b = subparsers.add_parser('command_b', help='command_b help')
parser_b.add_argument('--baz', choices='XYZ', help='baz help')
argv = ['--foo', 'command_a', '12', 'command_b', '--baz', 'Z']
while argv:
print(argv)
options, argv = parser.parse_known_args(argv)
print(options)
if not options.subparser_name:
break
To używa parse_known_args
zamiast parse_args
. parse_args
przerywa działanie po napotkaniu argumentu nieznanego bieżącemu subparserowi,parse_known_args
zwraca je jako drugą wartość w zwracanej krotce. W tym podejściu pozostałe argumenty są ponownie przekazywane do parsera. Dlatego dla każdego polecenia tworzona jest nowa przestrzeń nazw.
Zauważ, że w tym podstawowym przykładzie wszystkie opcje globalne są dodawane tylko do pierwszej opcji Przestrzeń nazw, a nie do kolejnych Przestrzeni nazw.
To podejście działa dobrze w większości sytuacji, ale ma trzy ważne ograniczenia:
- Nie jest możliwe użycie tego samego opcjonalnego argumentu dla różnych podpoleceń, takich jak
myprog.py command_a --foo=bar command_b --foo=bar
.
- Nie jest możliwe użycie argumentów pozycyjnych o zmiennej długości z komendami (
nargs='?'
lub nargs='+'
lub nargs='*'
).
- Każdy znany argument jest analizowany bez „przerywania” nowego polecenia. Np.
PROG --foo command_b command_a --baz Z 12
Z powyższym kodem --baz Z
zostanie zużyty przez command_b
, a nie przez command_a
.
Te ograniczenia są bezpośrednim ograniczeniem argparse. Oto prosty przykład, który pokazuje ograniczenia argparse-nawet w przypadku używania pojedynczej komendy-:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('spam', nargs='?')
subparsers = parser.add_subparsers(help='sub-command help', dest='subparser_name')
parser_a = subparsers.add_parser('command_a', help='command_a help')
parser_a.add_argument('bar', type=int, help='bar help')
parser_b = subparsers.add_parser('command_b', help='command_b help')
options = parser.parse_args('command_a 42'.split())
print(options)
To podniesie error: argument subparser_name: invalid choice: '42' (choose from 'command_a', 'command_b')
.
Przyczyną jest to, że metoda wewnętrzna argparse.ArgParser._parse_known_args()
jest zbyt chciwa i zakłada, że command_a
jest to wartość spam
argumentu opcjonalnego . W szczególności podczas „dzielenia” argumentów opcjonalnych i pozycyjnych _parse_known_args()
nie sprawdza nazw argumentów (takich jak command_a
lub command_b
), a jedynie miejsca, w których występują one na liście argumentów. Zakłada również, że każda podkomenda zużyje wszystkie pozostałe argumenty. To ograniczenie argparse
uniemożliwia również prawidłową implementację parserów obsługujących wiele poleceń. Oznacza to niestety, że prawidłowa implementacja wymaga pełnego przepisania argparse.ArgParser._parse_known_args()
metody, czyli ponad 200 linii kodu.
Biorąc pod uwagę te ograniczenia, może to być opcja powrotu do pojedynczego argumentu wielokrotnego wyboru zamiast podpoleceń:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--bar', type=int, help='bar help')
parser.add_argument('commands', nargs='*', metavar='COMMAND',
choices=['command_a', 'command_b'])
options = parser.parse_args('--bar 2 command_a command_b'.split())
print(options)
W informacjach o użytkowaniu można nawet wymienić różne polecenia, zobacz moją odpowiedź https://stackoverflow.com/a/49999185/428542