Afficher le message d'aide avec python argparse lorsque le script est appelé sans aucun argument

226

Cela pourrait être simple. Supposons que j'ai un programme qui utilise argparse pour traiter les arguments / options de la ligne de commande. Les éléments suivants imprimeront le message «aide»:

./myprogram -h

ou:

./myprogram --help

Mais, si j'exécute le script sans aucun argument, cela ne fait rien. Ce que je veux, c'est afficher le message d'utilisation quand il est appelé sans argument. Comment cela se fait-il?

musashiXXX
la source

Réponses:

273

Cette réponse vient de Steven Bethard sur les groupes Google . Je le redistribue ici pour faciliter l'accès aux personnes sans compte Google.

Vous pouvez remplacer le comportement par défaut de la errorméthode:

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

Notez que la solution ci-dessus imprimera le message d'aide chaque fois que la error méthode est déclenchée. Par exemple, test.py --blahimprimera également le message d'aide s'il --blahne s'agit pas d'une option valide.

Si vous souhaitez imprimer le message d'aide uniquement si aucun argument n'est fourni sur la ligne de commande, alors c'est peut-être toujours le moyen le plus simple:

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

Notez que parser.print_help()s'imprime sur stdout par défaut. Comme le suggère init_js , utilisez parser.print_help(sys.stderr)pour imprimer sur stderr.

unutbu
la source
Ouais ... c'est ce que je me demandais, s'il y avait un moyen pour argparse de gérer ce scénario. Merci!
musashiXXX
6
Dans la deuxième solution que j'utilise parser.print_usage()à la place de parser.print_help()- le message d'aide inclut l'utilisation, mais il est plus détaillé.
user2314737
5
J'aurais voté pour la deuxième partie de la réponse, mais passer outre error()me semble une idée terrible. Il sert un but différent, il n'est pas conçu pour imprimer une utilisation conviviale ou une aide.
Peterino
@Peterino - le remplacement se produit dans une classe enfant, donc cela ne devrait pas être un problème. C'est explicite.
Marcel Wilson
1
@unutbu MERVEILLEUX! Exactement ce dont j'avais besoin. Une question, cela peut-il également s'appliquer aux sous-commandes? Je reçois généralement juste `` Namespace (output = None) `. Comment puis-je facilement déclencher une erreur sur TOUTES les sous-commandes? Je voudrais déclencher une erreur là-bas.
Jonathan Komar
56

Au lieu d'écrire une classe, un try / except peut être utilisé à la place

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

L'avantage est que le flux de travail est plus clair et que vous n'avez pas besoin d'une classe de stub. L'inconvénient est que la première ligne «d'utilisation» est imprimée deux fois.

Cela nécessitera au moins un argument obligatoire. Sans arguments obligatoires, fournir zéro argument sur la ligne de commande est valide.

vacri
la source
moi aussi, je préfère cela à la réponse acceptée. L'ajout d'une classe est trop qualifié pour imprimer de l'aide lorsque les arguments sont inattendus. Laissez l'excellent module argparse gérer les cas d'erreur pour vous.
Nicole Finnie
7
Ce code imprime l'aide 2 fois si l' -hindicateur est utilisé et les impressions inutiles aident si l' --versionindicateur est utilisé. Pour atténuer ces problèmes, vous pouvez vérifier le type d'erreur comme ceci:except SystemExit as err: if err.code == 2: parser.print_help()
pkowalczyk
25

Avec argparse, vous pourriez faire:

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
la source
5
Cela doit arriver avant l'appel àparser.parse_args()
Bob Stein
18

Si vous avez des arguments qui doivent être spécifiés pour que le script s'exécute - utilisez le paramètre requis pour ArgumentParser comme indiqué ci-dessous: -

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

parse_args () signalera une erreur si le script est exécuté sans aucun argument.

pd321
la source
2
Il s'agit de la solution la plus simple qui fonctionnera également avec des options non valides.
Steve Scherer
1
D'accord. Je pense qu'il est toujours préférable de tirer parti des capacités intégrées de l'analyseur d'arguments, puis d'écrire un gestionnaire supplémentaire d'une sorte.
Christopher Hunter
18

Si vous associez des fonctions par défaut aux (sous) analyseurs, comme cela est mentionné ci-dessous add_subparsers, vous pouvez simplement l'ajouter comme action par défaut:

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

Ajoutez le try-except si vous déclenchez des exceptions en raison d'arguments de position manquants.

AManOfScience
la source
1
Cette réponse est tellement sous-estimée. Simple et fonctionne très bien avec les sous-analyseurs.
orodbhen
Très bonne réponse! Le seul changement que j'ai fait était d'utiliser un lambda sans paramètre.
boh717
12

La solution la plus propre sera de passer manuellement l'argument par défaut si aucun n'a été donné sur la ligne de commande:

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

Exemple complet:

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

Cela affichera une aide complète (pas une courte utilisation) si elle est appelée sans arguments.

Ievgen Popovych
la source
2
sys.argv[1:]est un idiome très courant. Je vois parser.parse_args(None if sys.argv[1:] else ['-h'])plus idiomatique et plus propre.
Nuno André
1
@ NunoAndré merci - a mis à jour la réponse. Sent plus pythonique en effet.
Ievgen Popovych
10

Jeter ma version dans la pile ici:

import argparse

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

Vous pouvez remarquer le parser.exit- je le fais principalement comme ça car il enregistre une ligne d'importation si c'était la seule raison sysdans le fichier ...

pauricthelodger
la source
parser.exit (1) est sympa! Bon ajout.
cgseller
4
Malheureusement, parser.parse_args () se terminera si un argument positionnel est manquant. Cela ne fonctionne donc que lors de l'utilisation d'arguments facultatifs.
Marcel Wilson
1
@MarcelWilson, c'est vrai - bonne prise! Je vais réfléchir à la façon de le changer.
pauricthelodger
not vars(args)peut ne pas fonctionner lorsque les arguments ont une defaultméthode.
funkid
5

Il y a une paire de doublures avec sys.argv[1:] (un idiome Python très courant pour référencer les arguments de la ligne de commande, étant sys.argv[0]le nom du script) qui peuvent faire le travail.

Le premier est explicite, propre et pythonique:

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

Le second est un peu plus hacké. Combiner le fait précédemment évalué qu'une liste vide est Falseavec le True == 1etFalse == 0 équivalences vous obtenez ceci:

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

Peut-être trop de crochets, mais assez clair si une sélection d'argument précédente a été faite.

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

La parser.exitméthode accepte également un status(code retour) et unmessage valeur (incluez vous-même une nouvelle ligne de fin!).

un exemple d'opinion, :)

#!/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

Exemples d'appels:

$ python3 ~ / helloworld.py; écho $?
utilisation: helloworld.py [-h] [--example]

 Exemple de fichier python basé sur argparser

arguments facultatifs:
  -h, --help afficher ce message d'aide et quitter
  --example Exemple d'argument

Je ne sais pas ce qui s'est mal passé, peut-être manquant - exemple de condition?
128
$ python3 ~ / helloworld.py --example; écho $?
0
ThorSummoner
la source
0

Définissez vos arguments positionnels avec des nargs et vérifiez si les arguments positionnels sont vides.

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

Référence Nargs Python

zerocog
la source
0

Voici une autre façon de le faire, si vous avez besoin de quelque chose de flexible où vous souhaitez afficher l'aide si des paramètres spécifiques sont passés, aucun du tout ou plus d'un argument conflictuel:

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

À votre santé!

radtek
la source
Je pense que vous seriez beaucoup plus facile d'utiliser des sous-analyseurs ou un groupe mutuellement_exclusif
Tim Bray
0

Si votre commande est quelque chose où un utilisateur doit choisir une action, utilisez un groupe mutuellement exclusif avec required = True .

C'est une sorte d'extension à la réponse donnée par 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')

Production:

$ python3 a_test.py
utilisation: a_test.py [-h] (--batch pay_id | --list | --all)
a_test.py: erreur: l'un des arguments --batch --list --all est requis

Cela ne donne que l'aide de base. Et certaines des autres réponses vous apporteront toute l'aide. Mais au moins, vos utilisateurs savent qu'ils peuvent faire -h

Tim Bray
la source
0

Ce n'est pas bon (aussi, car intercepte toutes les erreurs), mais:

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(...)
    ...

Voici la définition de la errorfonction de la ArgumentParserclasse:

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

. Comme vous le voyez, après la signature, il faut deux arguments. Cependant, les fonctions en dehors de la classe ne connaissent pas le premier argument: selfcar, en gros, c'est un paramètre pour la classe. (Je sais que tu sais ...) De ce fait, juste passer propre selfet messageen _error(...)ne peut pas (

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

    sys.exit(-1)

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

    parser.error = _error
    ...
...

affichera:

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

). Vous pouvez passer parser( self) en _errorfonction, en l'appelant:

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

    sys.exit(-1)

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

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

, mais vous ne voulez pas quitter le programme pour le moment. Puis retournez-le:

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

        sys.exit(-1)

    return wrapper
...

. Néanmoins, parserne sait pas, qu'il a été modifié, donc quand une erreur se produit, il en enverra la cause (à propos, sa traduction localisée). Eh bien, interceptez-le:

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

        sys.exit(-1)

    return wrapper
...

. Maintenant, quand une erreur se produit et parserenverra la cause de celle-ci, vous l'intercepterez, la regarderez et ... la jeterez.

Maxim Temny
la source