Python-Argument: Machen Sie mindestens ein Argument erforderlich

92

Ich habe argparsefür ein Python-Programm verwendet -process, das -uploadoder beides kann:

parser = argparse.ArgumentParser(description='Log archiver arguments.')
parser.add_argument('-process', action='store_true')
parser.add_argument('-upload',  action='store_true')
args = parser.parse_args()

Das Programm ist ohne mindestens einen Parameter bedeutungslos. Wie kann ich konfigurieren argparse, dass mindestens ein Parameter ausgewählt wird?

AKTUALISIEREN:

Folgen Sie den Kommentaren: Wie kann ein Programm mit mindestens einer Option pythonisch parametrisiert werden?

Adam Matan
quelle
9
-xist universell eine Flagge und optional. Schneiden Sie die, -wenn es erforderlich ist.
1
Könnten Sie nicht processdas Standardverhalten festlegen (ohne dass Optionen angegeben werden müssen) und dem Benutzer erlauben, dies zu ändern, uploadwenn diese Option festgelegt ist? Normalerweise sollten Optionen optional sein, daher der Name. Erforderliche Optionen sollten vermieden werden (dies ist auch in den argparse Dokumenten enthalten).
Tim Pietzcker
@AdamMatan Es ist fast drei Jahre her, dass Sie Ihre Frage gestellt haben, aber ich mochte die darin verborgene Herausforderung und nutzte den Vorteil, dass neue Lösungen für diese Art von Aufgaben verfügbar sind.
Jan Vlcinsky

Antworten:

105
if not (args.process or args.upload):
    parser.error('No action requested, add -process or -upload')
Phihag
quelle
1
argparseDies ist wahrscheinlich der einzige Weg, wenn hierfür keine Option integriert ist.
Adam Matan
27
args = vars(parser.parse_args())
if not any(args.values()):
    parser.error('No arguments provided.')
Brentlance
quelle
3
+1 für eine verallgemeinerte Lösung. Ebenso wie die Verwendung von vars(), die auch nützlich ist, um sorgfältig benannte Optionen mit ** an einen Konstruktor zu übergeben.
Lenna
Welches ist genau das, was ich damit mache. Vielen Dank!
Brentlance
1
Dang, das gefällt mir vars. Ich habe es einfach getan .__dict__und fühlte mich vorher dumm.
Theo Belaire
1
tolle Antworten. Sowohl "vars" als auch "any" waren neu für mich :-)
Vivek Jha
21

Wenn nicht der Teil 'oder beides' (ich habe dies anfangs verpasst), könnten Sie so etwas verwenden:

parser = argparse.ArgumentParser(description='Log archiver arguments.')
parser.add_argument('--process', action='store_const', const='process', dest='mode')
parser.add_argument('--upload',  action='store_const', const='upload', dest='mode')
args = parser.parse_args()
if not args.mode:
    parser.error("One of --process or --upload must be given")

Wahrscheinlich wäre es jedoch besser, stattdessen Unterbefehle zu verwenden.

Jacek Konieczny
quelle
4
Ich denke, er möchte --processOR zulassen --upload, nicht XOR. Dies verhindert, dass beide Optionen gleichzeitig eingestellt werden.
Phihag
+1, weil Sie Unterbefehle erwähnt haben. Doch - wie jemand in den Kommentaren gezeigt hat -xund --xxxin der Regel optionale Parameter sind.
Mac
20

Ich weiß, dass dies so alt wie Schmutz ist, aber der Weg, eine Option zu verlangen, aber mehr als eine (XOR) zu verbieten, ist wie folgt:

parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-process', action='store_true')
group.add_argument('-upload',  action='store_true')
args = parser.parse_args()
print args

Ausgabe:

>opt.py  
usage: multiplot.py [-h] (-process | -upload)  
multiplot.py: error: one of the arguments -process -upload is required  

>opt.py -upload  
Namespace(process=False, upload=True)  

>opt.py -process  
Namespace(process=True, upload=False)  

>opt.py -upload -process  
usage: multiplot.py [-h] (-process | -upload)  
multiplot.py: error: argument -process: not allowed with argument -upload  
Knut
quelle
3
Leider will das OP kein XOR. Es ist eines oder beide, aber keines, sodass Ihr letzter Testfall nicht den Anforderungen entspricht.
kdopen
2
@kdopen: Der Befragte hat klargestellt, dass dies eine Variation der ursprünglichen Frage ist, die ich als nützlich empfand: "Die Möglichkeit, eine Option zu fordern, aber mehr als eine zu verbieten" Vielleicht würde die Etikette von Stack Exchange stattdessen eine neue Frage erfordern . Aber diese Antwort hier zu haben hat mir geholfen ...
erik.weathers
2
Dieser Beitrag beantwortet nicht die erste Frage
Marc
2
Wie beantwortet dies die Frage nach "mindestens einem"?
Xaxxon
2
Leider will das OP kein XOR.
duckman_1991
8

Anforderungsüberprüfung

  • benutze argparse(ich werde diesen ignorieren)
  • Lassen Sie eine oder zwei Aktionen aufrufen (mindestens eine erforderlich).
  • versuche es mit Pythonic (ich würde es lieber "POSIX" -ähnlich nennen)

Es gibt auch einige implizite Anforderungen, wenn Sie in der Befehlszeile leben:

  • Erklären Sie dem Benutzer die Verwendung auf leicht verständliche Weise
  • Optionen sind optional
  • Ermöglichen die Angabe von Flags und Optionen
  • Ermöglichen das Kombinieren mit anderen Parametern (wie Dateiname oder Namen).

Beispiellösung mit docopt(Datei managelog.py):

"""Manage logfiles
Usage:
    managelog.py [options] process -- <logfile>...
    managelog.py [options] upload -- <logfile>...
    managelog.py [options] process upload -- <logfile>...
    managelog.py -h

Options:
    -V, --verbose      Be verbose
    -U, --user <user>  Username
    -P, --pswd <pswd>  Password

Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>
"""
if __name__ == "__main__":
    from docopt import docopt
    args = docopt(__doc__)
    print args

Versuchen Sie es auszuführen:

$ python managelog.py
Usage:
    managelog.py [options] process -- <logfile>...
    managelog.py [options] upload -- <logfile>...
    managelog.py [options] process upload -- <logfile>...
    managelog.py -h

Zeigen Sie die Hilfe:

$ python managelog.py -h
Manage logfiles
Usage:
    managelog.py [options] process -- <logfile>...
    managelog.py [options] upload -- <logfile>...
    managelog.py [options] process upload -- <logfile>...
    managelog.py -h

Options:
    -V, --verbose      Be verbose
    -U, --user <user>  Username
    -P, --pswd <pswd>  P    managelog.py [options] upload -- <logfile>...

Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>

Und benutze es:

$ python managelog.py -V -U user -P secret upload -- alfa.log beta.log
{'--': True,
 '--pswd': 'secret',
 '--user': 'user',
 '--verbose': True,
 '-h': False,
 '<logfile>': ['alfa.log', 'beta.log'],
 'process': False,
 'upload': True}

Kurze Alternative short.py

Es kann noch kürzere Varianten geben:

"""Manage logfiles
Usage:
    short.py [options] (process|upload)... -- <logfile>...
    short.py -h

Options:
    -V, --verbose      Be verbose
    -U, --user <user>  Username
    -P, --pswd <pswd>  Password

Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>
"""
if __name__ == "__main__":
    from docopt import docopt
    args = docopt(__doc__)
    print args

Die Verwendung sieht folgendermaßen aus:

$ python short.py -V process upload  -- alfa.log beta.log
{'--': True,
 '--pswd': None,
 '--user': None,
 '--verbose': True,
 '-h': False,
 '<logfile>': ['alfa.log', 'beta.log'],
 'process': 1,
 'upload': 1}

Beachten Sie, dass anstelle von booleschen Werten für die Schlüssel "process" und "upload" Zähler vorhanden sind.

Es stellt sich heraus, dass wir die Verdoppelung dieser Wörter nicht verhindern können:

$ python short.py -V process process upload  -- alfa.log beta.log
{'--': True,
 '--pswd': None,
 '--user': None,
 '--verbose': True,
 '-h': False,
 '<logfile>': ['alfa.log', 'beta.log'],
 'process': 2,
 'upload': 1}

Schlussfolgerungen

Das Entwerfen einer guten Befehlszeilenschnittstelle kann manchmal eine Herausforderung sein.

Es gibt mehrere Aspekte eines befehlszeilenbasierten Programms:

  • gutes Design der Kommandozeile
  • Auswahl / Verwendung des richtigen Parsers

argparse bietet viel, schränkt aber mögliche Szenarien ein und kann sehr komplex werden.

Die docoptDinge werden viel kürzer, während die Lesbarkeit erhalten bleibt und ein hohes Maß an Flexibilität geboten wird. Wenn Sie es schaffen, analysierte Argumente aus dem Wörterbuch abzurufen und einige Konvertierungen (in Ganzzahlen, Öffnen von Dateien ...) manuell (oder in einer anderen aufgerufenen Bibliothek schema) docoptdurchzuführen, ist die Analyse der Befehlszeile möglicherweise gut geeignet.

Jan Vlcinsky
quelle
Noch nie von docopt gehört, toller Vorschlag!
Ton van den Heuvel
@TonvandenHeuvel Gut. Ich möchte nur bestätigen, dass ich es immer noch als meine bevorzugte Lösung für Befehlszeilenschnittstellen verwende.
Jan Vlcinsky
Beste Antwort evar, danke für die detaillierten Beispiele.
jnovack
5

Wenn Sie ein Python-Programm benötigen, das mit mindestens einem Parameter ausgeführt werden soll, fügen Sie ein Argument hinzu, das nicht über das Optionspräfix (- oder - standardmäßig) verfügt, und setzen Sie nargs=+(mindestens ein Argument erforderlich). Das Problem mit dieser Methode, das ich gefunden habe, ist, dass argparse, wenn Sie das Argument nicht angeben, einen Fehler "zu wenige Argumente" generiert und das Hilfemenü nicht druckt. Wenn Sie diese Funktionalität nicht benötigen, gehen Sie wie folgt in Code vor:

import argparse

parser = argparse.ArgumentParser(description='Your program description')
parser.add_argument('command', nargs="+", help='describe what a command is')
args = parser.parse_args()

Ich denke , wenn Sie ein Argument mit den Optionspräfixen hinzufügen, regiert nargs den gesamten Argumentparser und nicht nur die Option. (Was ich meine ist, wenn Sie eine --optionFahne mit nargs="+", dann --optionFlagge erwartet mindestens ein Argument. Wenn Sie optionmit nargs="+", es erwartet mindestens ein Argument insgesamt.)

NuclearPeon
quelle
Sie könnten choices=['process','upload']diesem Argument hinzufügen .
hpaulj
5

Für http://bugs.python.org/issue11588 untersuche ich Möglichkeiten, das mutually_exclusive_groupKonzept zu verallgemeinern, um solche Fälle zu behandeln.

Mit dieser Entwicklung argparse.py, https://github.com/hpaulj/argparse_issues/blob/nested/argparse.py kann ich schreiben:

parser = argparse.ArgumentParser(prog='PROG', 
    description='Log archiver arguments.')
group = parser.add_usage_group(kind='any', required=True,
    title='possible actions (at least one is required)')
group.add_argument('-p', '--process', action='store_true')
group.add_argument('-u', '--upload',  action='store_true')
args = parser.parse_args()
print(args)

welches Folgendes erzeugt help:

usage: PROG [-h] (-p | -u)

Log archiver arguments.

optional arguments:
  -h, --help     show this help message and exit

possible actions (at least one is required):
  -p, --process
  -u, --upload

Dies akzeptiert Eingaben wie '-u', '-up', '--proc --up' usw.

Am Ende wird ein Test ausgeführt, der https://stackoverflow.com/a/6723066/901925 ähnelt , obwohl die Fehlermeldung klarer sein muss:

usage: PROG [-h] (-p | -u)
PROG: error: some of the arguments process upload is required

Ich wundere mich:

  • Sind die Parameter kind='any', required=Trueklar genug (akzeptieren Sie einen der Gruppen; mindestens einer ist erforderlich)?

  • Ist die Verwendung (-p | -u)klar? Eine erforderliche gegenseitig ausschließende Gruppe erzeugt dasselbe. Gibt es eine alternative Notation?

  • Ist die Verwendung einer solchen Gruppe intuitiver als ein phihag'seinfacher Test?

hpaulj
quelle
Ich kann add_usage_groupauf dieser Seite keine Erwähnung finden : docs.python.org/2/library/argparse.html ; Geben Sie bitte einen Link zur Dokumentation an.
P. Myer Nore
@ P.MyerNore, ich habe einen Link bereitgestellt - zu Beginn dieser Antwort. Dies wurde nicht in Produktion genommen.
Hpaulj
5

Der beste Weg, dies zu tun, ist die Verwendung des in Python eingebauten Moduls add_mutually_exclusive_group .

parser = argparse.ArgumentParser(description='Log archiver arguments.')
group = parser.add_mutually_exclusive_group()
group.add_argument('-process', action='store_true')
group.add_argument('-upload',  action='store_true')
args = parser.parse_args()

Wenn Sie möchten, dass nur ein Argument über die Befehlszeile ausgewählt wird, verwenden Sie einfach required = True als Argument für die Gruppe

group = parser.add_mutually_exclusive_group(required=True)
faizan baig
quelle
2
Wie bringt Sie das "mindestens eins" - bringt es Sie nicht "genau eins"?
Xaxxon
3
Leider will das OP kein XOR. OP sucht nach OR
duckman_1991
Dies beantwortete die Frage von OP nicht, aber es beantwortete meine, also trotzdem danke ¯_ (ツ) _ / ¯
rosstex
2

Vielleicht Unterparser verwenden?

import argparse

parser = argparse.ArgumentParser(description='Log archiver arguments.')
subparsers = parser.add_subparsers(dest='subparser_name', help='sub-command help')
parser_process = subparsers.add_parser('process', help='Process logs')
parser_upload = subparsers.add_parser('upload', help='Upload logs')
args = parser.parse_args()

print("Subparser: ", args.subparser_name)

Jetzt --helpzeigt:

$ python /tmp/aaa.py --help
usage: aaa.py [-h] {process,upload} ...

Log archiver arguments.

positional arguments:
  {process,upload}  sub-command help
    process         Process logs
    upload          Upload logs

optional arguments:
  -h, --help        show this help message and exit
$ python /tmp/aaa.py
usage: aaa.py [-h] {process,upload} ...
aaa.py: error: too few arguments
$ python3 /tmp/aaa.py upload
Subparser:  upload

Sie können diesen Unterparsern auch zusätzliche Optionen hinzufügen. Anstatt dies zu verwenden, können dest='subparser_name'Sie auch Funktionen binden, die bei einem bestimmten Unterbefehl direkt aufgerufen werden sollen (siehe Dokumente).

jhutar
quelle
2

Dies erfüllt den Zweck und dies wird auch in der automatisch generierten argparse- --helpAusgabe berücksichtigt, was imho das ist, was die meisten vernünftigen Programmierer wollen (funktioniert auch mit optionalen Argumenten):

parser.add_argument(
    'commands',
    nargs='+',                      # require at least 1
    choices=['process', 'upload'],  # restrict the choice
    help='commands to execute'
)

Offizielle Dokumente dazu: https://docs.python.org/3/library/argparse.html#choices

Bob
quelle
1

Verwenden Sie append_const für eine Liste von Aktionen und überprüfen Sie, ob die Liste ausgefüllt ist:

parser.add_argument('-process', dest=actions, const="process", action='append_const')
parser.add_argument('-upload',  dest=actions, const="upload", action='append_const')

args = parser.parse_args()

if(args.actions == None):
    parser.error('Error: No actions requested')

Sie können die Methoden sogar direkt in den Konstanten angeben.

def upload:
    ...

parser.add_argument('-upload',  dest=actions, const=upload, action='append_const')
args = parser.parse_args()

if(args.actions == None):
    parser.error('Error: No actions requested')

else:
    for action in args.actions:
        action()
Storm_m2138
quelle