Die von @Vikas bereitgestellte Lösung schlägt für unterbefehlsspezifische optionale Argumente fehl, der Ansatz ist jedoch gültig. Hier ist eine verbesserte Version:
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
Dies verwendet parse_known_args
anstelle von parse_args
. parse_args
bricht ab, sobald ein dem aktuellen Subparser unbekanntes Argument gefunden wird.parse_known_args
gibt es als zweiten Wert im zurückgegebenen Tupel zurück. Bei diesem Ansatz werden die verbleibenden Argumente erneut dem Parser zugeführt. Daher wird für jeden Befehl ein neuer Namespace erstellt.
Beachten Sie, dass in diesem grundlegenden Beispiel alle globalen Optionen nur dem Namespace der ersten Optionen und nicht den nachfolgenden Namespaces hinzugefügt werden.
Dieser Ansatz funktioniert in den meisten Situationen einwandfrei, weist jedoch drei wichtige Einschränkungen auf:
- Es ist nicht möglich, dasselbe optionale Argument für verschiedene Unterbefehle wie zu verwenden
myprog.py command_a --foo=bar command_b --foo=bar
.
- Es ist nicht möglich, Positionsargumente variabler Länge mit Unterbefehlen (
nargs='?'
oder nargs='+'
oder nargs='*'
) zu verwenden.
- Jedes bekannte Argument wird analysiert, ohne den neuen Befehl zu "brechen". ZB in
PROG --foo command_b command_a --baz Z 12
mit dem obigen Code, --baz Z
wird von verbraucht command_b
, nicht von command_a
.
Diese Einschränkungen sind eine direkte Einschränkung von Argparse. Hier ist ein einfaches Beispiel, das die Einschränkungen von argparse zeigt - auch wenn ein einzelner Unterbefehl verwendet wird:
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)
Dies wird die erhöhen error: argument subparser_name: invalid choice: '42' (choose from 'command_a', 'command_b')
.
Die Ursache ist, dass die interne Methode argparse.ArgParser._parse_known_args()
zu gierig ist und davon ausgeht, dass dies command_a
der Wert des optionalen spam
Arguments ist. Insbesondere beim Aufteilen von optionalen und positionellen Argumenten werden _parse_known_args()
nicht die Namen der Arugments (wie command_a
oder command_b
) berücksichtigt , sondern nur, wo sie in der Argumentliste vorkommen. Es wird auch davon ausgegangen, dass jeder Unterbefehl alle verbleibenden Argumente verbraucht. Diese Einschränkung argparse
verhindert auch eine ordnungsgemäße Implementierung von Unterbefehlen mit mehreren Befehlen. Dies bedeutet leider, dass eine ordnungsgemäße Implementierung ein vollständiges Umschreiben der argparse.ArgParser._parse_known_args()
Methode erfordert , dh mehr als 200 Codezeilen.
Angesichts dieser Einschränkung kann es eine Option sein, einfach auf ein einzelnes Multiple-Choice-Argument anstelle von Unterbefehlen zurückzugreifen:
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)
Es ist sogar möglich, die verschiedenen Befehle in den Verwendungsinformationen aufzulisten, siehe meine Antwort https://stackoverflow.com/a/49999185/428542