Python argparse gegenseitige exklusive Gruppe

87

Was ich brauche ist:

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

Ich habe es versucht, aber es funktioniert nicht. Könnte mir jemand helfen?

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")

Vielen Dank!

Sean
quelle
Einstecken, aber ich wollte meine Bibliothek joffrey erwähnen . Ermöglicht es Ihnen, beispielsweise das zu tun, was diese Frage will, ohne Unterbefehle zu verwenden (wie in der akzeptierten Antwort) oder alles selbst zu validieren (wie in der zweithöchsten Antwort).
MI Wright

Antworten:

106

add_mutually_exclusive_groupschließt nicht aus, dass sich eine ganze Gruppe gegenseitig ausschließt. Optionen innerhalb der Gruppe schließen sich gegenseitig aus.

Was Sie suchen, sind Unterbefehle . Anstelle von prog [-a xxxx | [-b yyy -c zzz]] hätten Sie:

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

So rufen Sie mit den ersten Argumenten auf:

prog command_1 -a xxxx

So rufen Sie mit dem zweiten Satz von Argumenten auf:

prog command_2 -b yyyy -c zzzz

Sie können die Unterbefehlsargumente auch als positionell festlegen.

prog command_1 xxxx

Ein bisschen wie git oder svn:

git commit -am
git merge develop

Arbeitsbeispiel

# 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')

Probier es aus

>>> 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

Viel Glück.

Jonathan
quelle
Ich habe sie bereits unter eine Argumentationsgruppe gestellt. Wie kann ich in diesem Fall einen Unterbefehl hinzufügen? Vielen Dank!
Sean
1
Mit Beispielcode aktualisiert. Sie werden keine Gruppen verwenden, sondern Unterparser.
Jonathan
5
Aber wie würden Sie tun, was OP ursprünglich gefragt hat? Ich habe derzeit eine Reihe von Unterbefehlen, aber einer dieser Unterbefehle muss die Möglichkeit haben, zwischen[[-a <val>] | [-b <val1> -c <val2>]]
code_dredd
1
Dies beantwortet die Frage nicht, da Sie nicht "noname" -Befehle ausführen und das erreichen können, was OP verlangt[-a xxx | [-b yyy -c zzz]]
The Godfather
34

Während Jonathans Antwort für komplexe Optionen vollkommen in Ordnung ist, gibt es eine sehr einfache Lösung, die für die einfachen Fälle funktioniert, z. B. 1 Option schließt 2 andere Optionen wie in aus

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

oder sogar wie in der ursprünglichen Frage:

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

So würde ich es machen:

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)

Ich verwende hier Optionen, die einem Befehlszeilen-Wrapper zum Abfragen eines Mongodb gegeben wurden. Die collectionInstanz kann entweder die Methode aufrufen aggregateoder die Methode findmit dem fakultativen Argumente queryund fieldsdamit Sie sehen, warum die ersten beiden Argumente sind kompatibel und die letzte ist nicht.

Jetzt laufe ich parser.parse_args()und überprüfe den Inhalt:

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)

Natürlich funktioniert dieser kleine Hack nur in einfachen Fällen und es wäre ein Albtraum, alle möglichen Optionen zu prüfen, wenn Sie viele sich gegenseitig ausschließende Optionen und Gruppen haben. In diesem Fall sollten Sie Ihre Optionen auf Befehlsgruppen aufteilen, wie von Jonathan vorgeschlagen.

Oz123
quelle
5
Ich würde dies in diesem Fall nicht als "Hack" bezeichnen, da es sowohl lesbarer als auch überschaubarer erscheint - danke, dass Sie darauf hingewiesen haben!
Salbei
13
Ein noch besserer Weg wäre zu verwenden parser.error("-a and -q ..."). Auf diese Weise wird die vollständige Verwendungshilfe automatisch ausgedruckt.
WGH
Bitte beachten Sie, dass Sie in diesem Fall auch die folgenden Fälle validieren müssen: (1) beide qund fin der ersten Gruppe erforderlich ist Benutzer, (2) eine der Gruppen ist erforderlich. Und das macht die "einfache" Lösung nicht mehr so ​​einfach. Daher würde ich zustimmen, dass dies mehr Hack für handgefertigte Skripte ist, aber keine echte Lösung
The Godfather
4

Es gibt einen Python-Patch (in Entwicklung), mit dem Sie dies tun können.
http://bugs.python.org/issue10984

Die Idee ist, überlappende sich gegenseitig ausschließende Gruppen zuzulassen. So usagekönnte aussehen:

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

Das Ändern des Argparse-Codes, damit Sie zwei Gruppen wie diese erstellen können, war der einfache Teil. Das Ändern des usageFormatierungscodes erfordert das Schreiben eines benutzerdefinierten Codes HelpFormatter.

In wirken argparsesich Aktionsgruppen nicht auf das Parsen aus. Sie sind nur ein helpFormatierungswerkzeug. In der helpGruppe wirken sich gegenseitig ausschließende Gruppen nur auf die usageZeile aus. Beim Parsen, die parserverwendet die sich gegenseitig ausschließende Gruppen ein Wörterbuch der möglichen Konflikte zu konstruieren ( akann nicht auftreten , mit boder c, bkann mit nicht auftreten a, usw.), und dann wirft einen Fehler , wenn ein Konflikt entsteht.

Ohne diese argparse Patch, ich denke , die beste Wahl , um den Namespace produziert zu testen , ist parse_argsselbst (zB wenn beide aund bhaben Nicht - Standardwerte), und heben Sie Ihre eigenen Fehler. Sie können sogar den eigenen Fehlermechanismus des Parsers verwenden.

parser.error('custom error message')
hpaulj
quelle
1
Python-Problem: bugs.python.org/issue11588 untersucht Möglichkeiten, wie Sie benutzerdefinierte exklusive / inklusive Tests schreiben können.
Hpaulj