Argparse: Erforderliche Argumente unter "optionale Argumente"?

229

Ich verwende den folgenden einfachen Code, um einige Argumente zu analysieren. Beachten Sie, dass einer von ihnen erforderlich ist. Wenn der Benutzer das Skript ohne Angabe des Arguments ausführt, zeigt der angezeigte Verwendungs- / Hilfetext leider nicht an, dass es ein nicht optionales Argument gibt, was ich sehr verwirrend finde. Wie kann ich Python dazu bringen, anzuzeigen, dass ein Argument nicht optional ist?

Hier ist der Code:

import argparse
if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description='Foo')
    parser.add_argument('-i','--input', help='Input file name', required=True)
    parser.add_argument('-o','--output', help='Output file name', default="stdout")
    args = parser.parse_args()
    print ("Input file: %s" % args.input )
    print ("Output file: %s" % args.output )

Wenn ich den obigen Code ohne Angabe des erforderlichen Arguments ausführe, erhalte ich die folgende Ausgabe:

usage: foo.py [-h] -i INPUT [-o OUTPUT]

Foo

optional arguments:
    -h, --help            show this help message and exit
    -i INPUT, --input INPUT
                          Input file name
    -o OUTPUT, --output OUTPUT
                          Output file name
mort
quelle
5
In der Verwendungszeile ist das -i INPUTTeil nicht von eckigen Klammern umgeben, was subtil anzeigt, dass dies tatsächlich erforderlich ist. Sie können aber auch manuell erklären , dass durch die helpparam
Jaime RGP
7
@JaimeRGP Ja, aber das reicht natürlich nicht aus, und es ist auch weniger als prominent. Der zugewiesene Gruppenname optional argumentsfür die erforderlichen Argumente ist immer noch irreführend.
Acumenus

Antworten:

315

Parameter, die mit -oder beginnen, --werden normalerweise als optional betrachtet. Alle anderen Parameter sind Positionsparameter und als solche vom Design erforderlich (wie Positionsfunktionsargumente). Es ist möglich, optionale Argumente zu verlangen, aber dies ist ein bisschen gegen ihr Design. Da sie immer noch Teil der nicht positionellen Argumente sind, werden sie auch dann unter der verwirrenden Überschrift "optionale Argumente" aufgeführt, wenn sie erforderlich sind. Die fehlenden eckigen Klammern im Verwendungsteil zeigen jedoch, dass sie tatsächlich erforderlich sind.

Siehe auch die Dokumentation :

Im Allgemeinen geht das argparse-Modul davon aus, dass Flags wie -f und --bar optionale Argumente angeben, die in der Befehlszeile immer weggelassen werden können.

Hinweis: Erforderliche Optionen werden im Allgemeinen als schlechte Form angesehen, da Benutzer erwarten, dass Optionen optional sind, und sie daher nach Möglichkeit vermieden werden sollten.

Davon abgesehen werden die Überschriften "Positionsargumente" und "optionale Argumente" in der Hilfe von zwei Argumentgruppen generiert, in die die Argumente automatisch unterteilt werden. Jetzt können Sie sich "hineinhacken" und den Namen der optionalen Argumente ändern. Eine weitaus elegantere Lösung wäre jedoch, eine weitere Gruppe für "erforderliche benannte Argumente" (oder wie auch immer Sie sie nennen möchten) zu erstellen:

parser = argparse.ArgumentParser(description='Foo')
parser.add_argument('-o', '--output', help='Output file name', default='stdout')
requiredNamed = parser.add_argument_group('required named arguments')
requiredNamed.add_argument('-i', '--input', help='Input file name', required=True)
parser.parse_args(['-h'])
usage: [-h] [-o OUTPUT] -i INPUT

Foo

optional arguments:
  -h, --help            show this help message and exit
  -o OUTPUT, --output OUTPUT
                        Output file name

required named arguments:
  -i INPUT, --input INPUT
                        Input file name
Sack
quelle
Ich hatte das gleiche Problem. Ich habe versucht, Sie Lösung. Es fügt der neuen Gruppe zwar die Argumente hinzu, aber mein Code scheint danach nicht mehr zu funktionieren. Alle Lösungen wäre dankbar. Link zu meinem Code - pastebin.com/PvC2aujz
Zarar Mahmud
1
@ZararMahmud: Sie übergeben leere Argumente in Zeile 24 Ihres Codes: parser.parse_args([])Verwenden Sie stattdessen parser.parse_args()keine Argumente, um den Inhalt von sys.argv zu erfassen. Per argparse
Devin
@poke: Schöne Lösung! Aber das hilft nicht, wenn Sie gegenseitige exklusive Gruppen benötigen oder mir etwas fehlt?
Richter
@Judge Ich würde empfehlen, diese pymotw.com/3/argparse/#mutually-exclusive-options
Peter Moore
78

Da ich die erforderlichen Argumente lieber vor dem optionalen auflisten möchte, hacke ich sie herum über:

    parser = argparse.ArgumentParser()
    parser._action_groups.pop()
    required = parser.add_argument_group('required arguments')
    optional = parser.add_argument_group('optional arguments')
    required.add_argument('--required_arg', required=True)
    optional.add_argument('--optional_arg')
    return parser.parse_args()

und dies gibt aus:

usage: main.py [-h] [--required_arg REQUIRED_ARG]
               [--optional_arg OPTIONAL_ARG]

required arguments:
  --required_arg REQUIRED_ARG

optional arguments:
  --optional_arg OPTIONAL_ARG

Ich kann leben, ohne dass 'Hilfe' in der Gruppe der optionalen Argumente auftaucht.

Karl Rosaen
quelle
3
Erzwingt dies tatsächlich, dass argparse eines der Argumente nach Bedarf behandelt?
Anthony
6
Ich denke, das 'erforderliche' Argument muss noch gesetzt werden, wenn ein Argument hinzugefügt wird.
Karl Rosaen
Das ist wirklich schön.
Paul Cezanne
7
@Anthony - nein, dafür brauchst du das 'required = True' in add_argument. Die obige Antwort veranschaulicht nur die Argumentgruppierung.
user2275693
47

Aufbau von @Karl Rosaen

parser = argparse.ArgumentParser()
optional = parser._action_groups.pop() # Edited this line
required = parser.add_argument_group('required arguments')
# remove this line: optional = parser...
required.add_argument('--required_arg', required=True)
optional.add_argument('--optional_arg')
parser._action_groups.append(optional) # added this line
return parser.parse_args()

und dies gibt aus:

usage: main.py [-h] [--required_arg REQUIRED_ARG]
           [--optional_arg OPTIONAL_ARG]

required arguments:
  --required_arg REQUIRED_ARG

optional arguments:
  -h, --help                    show this help message and exit
  --optional_arg OPTIONAL_ARG
RalphyZ
quelle
1
Übrigens, gibt es Möglichkeiten (Methoden), wie Sie Zugriff erhalten, _action_groupohne auf ein geschütztes Mitglied zuzugreifen? In meinem Fall muss ich der bereits vorhandenen (benutzerdefinierten) Gruppe ein Argument hinzufügen.
Machin
Das ist toll. Löst das Element --help, das in einer zweiten optionalen Liste angezeigt wird.
Jeremy
Hinweis : Diese Antwort unterbricht die exponierte API. Überprüfen Sie die Antwort von Bryan_D unten.
lol
18

Noch einmal aus @RalphyZ aufbauen

Dieser bricht die exponierte API nicht.

from argparse import ArgumentParser, SUPPRESS
# Disable default help
parser = ArgumentParser(add_help=False)
required = parser.add_argument_group('required arguments')
optional = parser.add_argument_group('optional arguments')

# Add back help 
optional.add_argument(
    '-h',
    '--help',
    action='help',
    default=SUPPRESS,
    help='show this help message and exit'
)
required.add_argument('--required_arg', required=True)
optional.add_argument('--optional_arg')

Welches wird das gleiche wie oben zeigen und sollte zukünftige Versionen überleben:

usage: main.py [-h] [--required_arg REQUIRED_ARG]
           [--optional_arg OPTIONAL_ARG]

required arguments:
  --required_arg REQUIRED_ARG

optional arguments:
  -h, --help                    show this help message and exit
  --optional_arg OPTIONAL_ARG
Bryan_D
quelle
Können Sie erklären, wie die Antwort von RalphyZ die exponierte API zerstört?
Jeremysprofile
5
_action_groupsist nur für den internen Gebrauch bestimmt. Daher gibt es keine versatzübergreifende Kompatibilitätsgarantie.
Bryan_D