Zeigt die Hilfemeldung mit Python Argparse an, wenn das Skript ohne Argumente aufgerufen wird

226

Dies könnte eine einfache sein. Angenommen, ich habe ein Programm, das argparse verwendet, um Befehlszeilenargumente / -optionen zu verarbeiten. Im Folgenden wird die Hilfemeldung gedruckt:

./myprogram -h

oder:

./myprogram --help

Aber wenn ich das Skript ohne irgendwelche Argumente ausführe, macht es nichts. Ich möchte, dass die Verwendungsnachricht angezeigt wird, wenn sie ohne Argumente aufgerufen wird. Wie geht das?

musashiXXX
quelle

Antworten:

273

Diese Antwort stammt von Steven Bethard in Google-Gruppen . Ich poste es hier erneut, um Menschen ohne Google-Konto den Zugriff zu erleichtern.

Sie können das Standardverhalten der errorMethode überschreiben :

import argparse
import sys

class MyParser(argparse.ArgumentParser):
    def error(self, message):
        sys.stderr.write('error: %s\n' % message)
        self.print_help()
        sys.exit(2)

parser = MyParser()
parser.add_argument('foo', nargs='+')
args = parser.parse_args()

Beachten Sie, dass die obige Lösung die Hilfemeldung druckt, wenn die error Methode ausgelöst wird. Druckt beispielsweise test.py --blahauch die Hilfemeldung, wenn dies --blahkeine gültige Option ist.

Wenn Sie die Hilfemeldung nur drucken möchten, wenn in der Befehlszeile keine Argumente angegeben sind, ist dies möglicherweise immer noch der einfachste Weg:

import argparse
import sys

parser=argparse.ArgumentParser()
parser.add_argument('foo', nargs='+')
if len(sys.argv)==1:
    parser.print_help(sys.stderr)
    sys.exit(1)
args=parser.parse_args()

Beachten Sie, dass parser.print_help()standardmäßig auf Standard gedruckt wird. Verwenden Sie , wie in init_js vorgeschlagen , parser.print_help(sys.stderr)zum Drucken in stderr.

unutbu
quelle
Ja ... darüber habe ich mich gefragt, ob es für argparse eine Möglichkeit gibt, mit diesem Szenario umzugehen. Vielen Dank!
MusashiXXX
6
In der zweiten Lösung, die ich parser.print_usage()anstelle von verwende, enthält parser.print_help()die Hilfemeldung die Verwendung, ist jedoch ausführlicher.
user2314737
5
Ich hätte für den zweiten Teil der Antwort gestimmt, aber das Überschreiben error()scheint mir eine schreckliche Idee zu sein. Es dient einem anderen Zweck und ist nicht zum Drucken einer benutzerfreundlichen Verwendung oder Hilfe gedacht.
Peterino
@Peterino - Die Überschreibung erfolgt in einer untergeordneten Klasse, daher sollte dies kein Problem sein. Es ist explizit.
Marcel Wilson
1
@unutbu WUNDERBAR! Genau das, was ich brauchte. Eine Frage: Kann dies auch auf Unterbefehle angewendet werden? Normalerweise bekomme ich nur "Namespace (output = None)". Wie kann ich bei ALLEN Unterbefehlen einfach einen Fehler auslösen? Ich möchte dort einen Fehler auslösen.
Jonathan Komar
56

Anstatt eine Klasse zu schreiben, kann stattdessen ein try / Except verwendet werden

try:
    options = parser.parse_args()
except:
    parser.print_help()
    sys.exit(0)

Der Vorteil ist, dass der Workflow klarer ist und Sie keine Stub-Klasse benötigen. Der Nachteil ist, dass die erste Verwendungszeile zweimal gedruckt wird.

Dies erfordert mindestens ein verbindliches Argument. Ohne obligatorische Argumente ist die Angabe von null Argumenten in der Befehlszeile gültig.

vacri
quelle
Ich auch, ich ziehe dies der akzeptierten Antwort vor. Das Hinzufügen einer Klasse wird für die Druckhilfe überbewertet, wenn die Argumente unerwartet sind. Lassen Sie das hervorragende Modul argparse Fehlerfälle für Sie behandeln.
Nicole Finnie
7
Dieser Code druckt zweimal, wenn ein -hFlag verwendet wird, und unnötige Drucke helfen, wenn ein --versionFlag verwendet wird. Um diese Probleme zu mindern, können Sie den Fehlertyp wie except SystemExit as err: if err.code == 2: parser.print_help()
folgt
25

Mit Argparse könnten Sie tun:

parser.argparse.ArgumentParser()
#parser.add_args here

#sys.argv includes a list of elements starting with the program
if len(sys.argv) < 2:
    parser.print_usage()
    sys.exit(1)
cgseller
quelle
5
Dies muss vor dem Anruf kommenparser.parse_args()
Bob Stein
18

Wenn Sie Argumente haben, die angegeben werden müssen, damit das Skript ausgeführt werden kann, verwenden Sie den erforderlichen Parameter für ArgumentParser wie folgt: -

parser.add_argument('--foo', required=True)

parse_args () meldet einen Fehler, wenn das Skript ohne Argumente ausgeführt wird.

pd321
quelle
2
Dies ist die einfachste Lösung und funktioniert auch mit den angegebenen ungültigen Optionen.
Steve Scherer
1
Einverstanden. Ich denke, es ist immer besser, die eingebauten Fähigkeiten des Argument-Parsers zu nutzen, als einen zusätzlichen Handler zu schreiben.
Christopher Hunter
18

Wenn Sie Standardfunktionen für (Unter-) Parser zuordnen, wie unter erwähnt add_subparsers, können Sie diese einfach als Standardaktion hinzufügen:

parser = argparse.ArgumentParser()
parser.set_defaults(func=lambda x: parser.print_usage())
args = parser.parse_args()
args.func(args)

Fügen Sie den Versuch hinzu, außer wenn Sie Ausnahmen aufgrund fehlender Positionsargumente auslösen.

AManOfScience
quelle
1
Diese Antwort wird so unterschätzt. Einfach und funktioniert sehr gut mit Unterparsern.
Orodbhen
Gute Antwort! Die einzige Änderung, die ich vorgenommen habe, war die Verwendung eines Lambda ohne Parameter.
Boh717
12

Die sauberste Lösung besteht darin, das Standardargument manuell zu übergeben, wenn in der Befehlszeile keines angegeben wurde:

parser.parse_args(args=None if sys.argv[1:] else ['--help'])

Vollständiges Beispiel:

import argparse, sys

parser = argparse.ArgumentParser()
parser.add_argument('--host', default='localhost', help='Host to connect to')
# parse arguments
args = parser.parse_args(args=None if sys.argv[1:] else ['--help'])

# use your args
print("connecting to {}".format(args.host))

Dies gibt die vollständige Hilfe aus (keine kurze Verwendung), wenn sie ohne Argumente aufgerufen wird.

Ievgen Popovych
quelle
2
sys.argv[1:]ist eine sehr verbreitete Redewendung. Ich sehe parser.parse_args(None if sys.argv[1:] else ['-h'])idiomatischer und sauberer.
Nuno André
1
@ NunoAndré danke - hat die Antwort aktualisiert. Fühlt sich in der Tat pythonischer an.
Ievgen Popovych
10

Wirf meine Version hier auf den Stapel:

import argparse

parser = argparse.ArgumentParser()
args = parser.parse_args()
if not vars(args):
    parser.print_help()
    parser.exit(1)

Sie werden vielleicht bemerken, dass parser.exit- ich mache es hauptsächlich so, weil es eine Importzeile speichert, wenn dies der einzige Grund sysin der Datei war ...

pauricthelodger
quelle
parser.exit (1) ist nett! Gute Ergänzung.
Cgseller
4
Leider wird parser.parse_args () beendet, wenn ein Positionsargument fehlt. Dies funktioniert also nur, wenn optionale Argumente verwendet werden.
Marcel Wilson
1
@MarcelWilson, das tut es in der Tat - guter Fang! Ich werde darüber nachdenken, wie ich es ändern kann.
Pauricthelodger
not vars(args)funktioniert möglicherweise nicht, wenn Argumente eine defaultMethode haben.
Funkid
5

Es gibt ein Paar Einzeiler mit sys.argv[1:](eine sehr verbreitete Python-Redewendung, um auf die Befehlszeilenargumente zu verweisen, wobei es sich um sys.argv[0]den Namen des Skripts handelt), die diese Aufgabe erfüllen können.

Der erste ist selbsterklärend, sauber und pythonisch:

args = parser.parse_args(None if sys.argv[1:] else ['-h'])

Der zweite ist etwas hackiger. Die Kombination der zuvor bewerteten Tatsache , dass eine leere Liste ist Falsemit der True == 1und False == 0Äquivalenzen Sie diese:

args = parser.parse_args([None, ['-h']][not sys.argv[1:]])

Vielleicht zu viele Klammern, aber ziemlich klar, wenn eine vorherige Argumentauswahl getroffen wurde.

_, *av = sys.argv
args = parser.parse_args([None, ['-h']][not av])
Nuno André
quelle
1
parser.print_help()
parser.exit()

Die parser.exitMethode akzeptiert auch einen status(Rückkehrcode) und einen messageWert (einschließlich eines nachgestellten Zeilenumbruchs!).

ein meinungsbild, :)

#!/usr/bin/env python3

""" Example argparser based python file
"""

import argparse

ARGP = argparse.ArgumentParser(
    description=__doc__,
    formatter_class=argparse.RawTextHelpFormatter,
)
ARGP.add_argument('--example', action='store_true', help='Example Argument')


def main(argp=None):
    if argp is None:
        argp = ARGP.parse_args()  # pragma: no cover

    if 'soemthing_went_wrong' and not argp.example:
        ARGP.print_help()
        ARGP.exit(status=128, message="\nI just don't know what went wrong, maybe missing --example condition?\n")


if __name__ == '__main__':
    main()  # pragma: no cover

Beispielaufrufe:

$ python3 ~ / helloworld.py; echo $?
Verwendung: helloworld.py [-h] [--example]

 Beispiel einer argparser-basierten Python-Datei

optionale Argumente:
  -h, --help Diese Hilfemeldung anzeigen und beenden
  - Beispiel Beispielargument

Ich weiß nur nicht, was schief gelaufen ist, vielleicht fehlt - Beispielbedingung?
128
$ python3 ~ / helloworld.py --example; echo $?
0
ThorSummoner
quelle
0

Stellen Sie Ihre Positionsargumente mit nargs ein und prüfen Sie, ob die Positionsargumente leer sind.

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('file', nargs='?')
args = parser.parse_args()
if not args.file:
    parser.print_help()

Referenz Python nargs

Nullocog
quelle
0

Hier ist eine andere Möglichkeit, wenn Sie etwas Flexibles benötigen, bei dem Sie Hilfe anzeigen möchten, wenn bestimmte Parameter übergeben werden, keines oder mehr als 1 widersprüchliches Argument:

import argparse
import sys

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('-d', '--days', required=False,  help="Check mapped inventory that is x days old", default=None)
    parser.add_argument('-e', '--event', required=False, action="store", dest="event_id",
                        help="Check mapped inventory for a specific event", default=None)
    parser.add_argument('-b', '--broker', required=False, action="store", dest="broker_id",
                        help="Check mapped inventory for a broker", default=None)
    parser.add_argument('-k', '--keyword', required=False, action="store", dest="event_keyword",
                        help="Check mapped inventory for a specific event keyword", default=None)
    parser.add_argument('-p', '--product', required=False, action="store", dest="product_id",
                        help="Check mapped inventory for a specific product", default=None)
    parser.add_argument('-m', '--metadata', required=False, action="store", dest="metadata",
                        help="Check mapped inventory for specific metadata, good for debugging past tix", default=None)
    parser.add_argument('-u', '--update', required=False, action="store_true", dest="make_updates",
                        help="Update the event for a product if there is a difference, default No", default=False)
    args = parser.parse_args()

    days = args.days
    event_id = args.event_id
    broker_id = args.broker_id
    event_keyword = args.event_keyword
    product_id = args.product_id
    metadata = args.metadata
    make_updates = args.make_updates

    no_change_counter = 0
    change_counter = 0

    req_arg = bool(days) + bool(event_id) + bool(broker_id) + bool(product_id) + bool(event_keyword) + bool(metadata)
    if not req_arg:
        print("Need to specify days, broker id, event id, event keyword or past tickets full metadata")
        parser.print_help()
        sys.exit()
    elif req_arg != 1:
        print("More than one option specified. Need to specify only one required option")
        parser.print_help()
        sys.exit()

    # Processing logic here ...

Prost!

radtek
quelle
Ich denke, Sie würden viel einfacher Subparser oder gegenseitig ausschließende Gruppe verwenden
Tim Bray
0

Wenn Ihr Befehl ein Befehl ist, bei dem ein Benutzer eine Aktion auswählen muss, verwenden Sie eine sich gegenseitig ausschließende Gruppe mit required = True .

Dies ist eine Art Erweiterung der Antwort von pd321.

import argparse

parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--batch", action='store', type=int,  metavar='pay_id')
group.add_argument("--list", action='store_true')
group.add_argument("--all", action='store_true', help='check all payments')

args=parser.parse_args()

if args.batch:
    print('batch {}'.format(args.batch))

if args.list:
    print('list')

if args.all:
    print('all')

Ausgabe:

$ python3 a_test.py
Verwendung: a_test.py [-h] (--batch pay_id | --list | --all)
a_test.py: Fehler: Eines der Argumente --batch --list --all ist erforderlich

Dies gibt nur die grundlegende Hilfe. Und einige der anderen Antworten geben Ihnen die volle Hilfe. Aber zumindest wissen Ihre Benutzer, dass sie -h können

Tim Bray
quelle
0

Dies ist nicht gut (auch, weil alle Fehler abgefangen werden), aber:

def _error(parser):
    def wrapper(interceptor):
        parser.print_help()

        sys.exit(-1)

    return wrapper

def _args_get(args=sys.argv[1:]):
    parser = argparser.ArgumentParser()

    parser.error = _error(parser)

    parser.add_argument(...)
    ...

Hier ist die Definition der errorFunktion der ArgumentParserKlasse:

https://github.com/python/cpython/blob/276eb67c29d05a93fbc22eea5470282e73700d20/Lib/argparse.py#L2374

. Wie Sie sehen, werden nach der Signatur zwei Argumente benötigt. Funktionen außerhalb der Klasse kennen jedoch nichts über das erste Argument: selfDies ist grob gesagt ein Parameter für die Klasse. (Ich weiß, dass Sie wissen , ...) Dadurch nur eigene passieren selfund messagein _error(...)nicht (

def _error(self, message):
    self.print_help()

    sys.exit(-1)

def _args_get(args=sys.argv[1:]):
    parser = argparser.ArgumentParser()

    parser.error = _error
    ...
...

wird ausgegeben:

...
"AttributeError: 'str' object has no attribute 'print_help'"

). Sie können parser( self) in _errorfunction übergeben, indem Sie es aufrufen:

def _error(self, message):
    self.print_help()

    sys.exit(-1)

def _args_get(args=sys.argv[1:]):
    parser = argparser.ArgumentParser()

    parser.error = _error(parser)
    ...
...

, aber Sie möchten das Programm jetzt nicht beenden. Dann gib es zurück:

def _error(parser):
    def wrapper():
        parser.print_help()

        sys.exit(-1)

    return wrapper
...

. Trotzdem parserweiß es nicht, dass es geändert wurde. Wenn also ein Fehler auftritt, sendet es die Ursache dafür (übrigens seine lokalisierte Übersetzung). Nun, dann fangen Sie es ab:

def _error(parser):
    def wrapper(interceptor):
        parser.print_help()

        sys.exit(-1)

    return wrapper
...

. Wenn nun ein Fehler auftritt und parserdie Ursache dafür gesendet wird, werden Sie ihn abfangen, sich das ansehen und ... wegwerfen.

Maxim Temny
quelle