Visualizza il messaggio di aiuto con python argparse quando viene chiamato lo script senza alcun argomento


226

Questo potrebbe essere semplice. Supponiamo di avere un programma che utilizza argparse per elaborare argomenti / opzioni della riga di comando. Di seguito verrà stampato il messaggio "aiuto":

./myprogram -h

o:

./myprogram --help

Ma, se eseguo lo script senza alcun argomento, non fa nulla. Quello che voglio che faccia è visualizzare il messaggio di utilizzo quando viene chiamato senza argomenti. Come è fatto?

Risposte:


273

Questa risposta arriva da Steven Bethard sui gruppi di Google . Lo sto ripubblicando qui per facilitare l'accesso alle persone senza un account Google.

È possibile ignorare il comportamento predefinito del errormetodo:

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

Si noti che la soluzione precedente stamperà il messaggio di aiuto ogni volta error che viene attivato il metodo. Ad esempio, test.py --blahstamperà anche il messaggio di aiuto se --blahnon è un'opzione valida.

Se vuoi stampare il messaggio di aiuto solo se non vengono forniti argomenti sulla riga di comando, allora forse questo è ancora il modo più semplice:

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

Si noti che parser.print_help()stampa su stdout per impostazione predefinita. Come suggerisce init_js , usare parser.print_help(sys.stderr)per stampare su stderr.


Sì .. questo è quello che mi chiedevo, se c'era un modo per argparse di gestire questo scenario. Grazie!
musashiXXX,

6
Nella seconda soluzione che uso parser.print_usage()al posto di parser.print_help()- il messaggio di aiuto include l'uso ma è più dettagliato.
user2314737

5
Avrei votato per la seconda parte della risposta, ma ignorare mi error()sembra un'idea terribile. Ha uno scopo diverso, non è progettato per la stampa di un uso o di un aiuto amichevole.
Peterino,

@Peterino: l'override si sta verificando in una classe figlio, quindi questo non dovrebbe essere un problema. È esplicito
Marcel Wilson,

1
@unutbu WONDERFUL! Esattamente quello di cui avevo bisogno. Una domanda, può essere applicata anche ai sottocomandi? Di solito ottengo solo `` Namespace (output = None) `. Come posso attivare facilmente un errore su TUTTI i sottocomandi? Vorrei innescare un errore lì.
Jonathan Komar,

56

Invece di scrivere una classe, al suo posto può essere usato un tentativo / salvo

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

Il lato positivo è che il flusso di lavoro è più chiaro e non è necessaria una classe di stub. Il rovescio della medaglia è che la prima riga di "utilizzo" viene stampata due volte.

Ciò richiederà almeno un argomento obbligatorio. Senza argomenti obbligatori, è valido fornire zero argomenti sulla riga di comando.


anche io preferisco questo alla risposta accettata. L'aggiunta di una classe è eccessiva per la stampa di aiuto quando gli argomenti sono inattesi. Lascia che l'ottimo modulo argparse gestisca i casi di errore per te.
Nicole Finnie,

7
Questo codice -hconsente di stampare 2 volte se viene utilizzato il flag e le stampe non necessarie aiutano se --versionviene utilizzato il flag. Per mitigare questi problemi, puoi controllare il tipo di errore in questo modo:except SystemExit as err: if err.code == 2: parser.print_help()
pkowalczyk,

25

Con argparse potresti fare:

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)

5
Questo deve venire prima della chiamata aparser.parse_args()
Bob Stein,

18

Se hai argomenti che devono essere specificati per l'esecuzione dello script, utilizza il parametro richiesto per ArgumentParser come mostrato di seguito: -

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

parse_args () segnalerà un errore se lo script viene eseguito senza argomenti.


2
Questa è la soluzione più semplice e funzionerà anche con opzioni non valide specificate.
Steve Scherer,

1
Concordato. Penso che sia sempre meglio sfruttare le capacità incorporate del parser di argomenti piuttosto che scrivere un gestore aggiuntivo di qualche tipo.
Christopher Hunter,

18

Se si associano le funzioni predefinite per (sotto) parser, come menzionato sotto add_subparsers, è possibile semplicemente aggiungerlo come azione predefinita:

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

Aggiungi try-tranne se sollevi eccezioni a causa di argomenti posizionali mancanti.


1
Questa risposta è così sottovalutata. Semplice e funziona molto bene con i sub-parser.
orodbhen,

Bella risposta! L'unica modifica che ho apportato è stata l'utilizzo di una lambda senza parametro.
boh717,

12

La soluzione più pulita sarà quella di passare manualmente l'argomento predefinito se nessuno è stato fornito sulla riga di comando:

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

Esempio completo:

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

Questo stamperà la guida completa (non un breve utilizzo) se chiamato senza argomenti.


2
sys.argv[1:]è un linguaggio molto comune. Vedo parser.parse_args(None if sys.argv[1:] else ['-h'])più idiomatico e più pulito.
Nuno André,

1
@ NunoAndré grazie - aggiornata la risposta. Sembra davvero più pitonico.
Ievgen Popovych,

10

Lancio della mia versione nella pila qui:

import argparse

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

Potresti notare che parser.exit- lo faccio principalmente in quel modo perché salva una riga di importazione se questo era l'unico motivo per sysnel file ...


parser.exit (1) è carino! Buona aggiunta.
cgseller,

4
Sfortunatamente parser.parse_args () uscirà se manca un argomento posizionale. Quindi funziona solo quando si usano argomenti opzionali.
Marcel Wilson,

1
@MarcelWilson, davvero - buona cattura! Penserò a come cambiarlo.
pauricthelodger

not vars(args)potrebbe non funzionare quando gli argomenti hanno defaultmetodo.
funkid,

5

Ci sono un paio di righe con sys.argv[1:](un linguaggio molto comune di Python per riferirsi agli argomenti della riga di comando, essendo sys.argv[0]il nome dello script) che possono fare il lavoro.

Il primo è autoesplicativo, pulito e pitonico:

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

Il secondo è un po 'hacker. Combinando il fatto in precedenza valutato che una lista vuota è Falsecon l' True == 1e False == 0equivalenze si ottiene questo:

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

Forse troppe parentesi, ma abbastanza chiaro se è stata fatta una precedente selezione di argomenti.

_, *av = sys.argv
args = parser.parse_args([None, ['-h']][not av])

1
parser.print_help()
parser.exit()

Il parser.exitmetodo accetta anche a status(codice di ritorno) e amessage valore (includi tu stesso una nuova riga finale!).

un esempio supponente, :)

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

Chiamate di esempio:

$ python3 ~ / helloworld.py; echo $?
utilizzo: helloworld.py [-h] [--esempio]

 Esempio di file python basato su argparser

argomenti opzionali:
  -h, --help mostra questo messaggio di aiuto ed esce
  --example Esempio di argomento

Semplicemente non so cosa sia andato storto, forse mancante - condizione di esempio?
128
$ python3 ~ / helloworld.py --example; echo $?
0

0

Imposta i tuoi argomenti posizionali con nargs e controlla se gli argomenti posizionali sono vuoti.

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

Nargs Python di riferimento


0

Ecco un altro modo per farlo, se hai bisogno di qualcosa di flessibile in cui vuoi mostrare aiuto se vengono passati parametri specifici, nessuno o più di 1 argomento in conflitto:

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

Saluti!


Penso che sarebbe molto più facile usare subparser o mutually_exclusive_group
Tim Bray

0

Se il tuo comando è qualcosa in cui un utente deve scegliere alcune azioni, usa un gruppo che si esclude a vicenda con richiesto = Vero .

Questo è un tipo di estensione alla risposta data da 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')

Produzione:

$ python3 a_test.py
utilizzo: a_test.py [-h] (--batch pay_id | --list | --all)
a_test.py: errore: è richiesto uno degli argomenti --batch --list --all

Questo dà solo l'aiuto di base. E alcune delle altre risposte ti daranno il pieno aiuto. Ma almeno i tuoi utenti sanno che possono fare -h


0

Questo non va bene (anche perché intercetta tutti gli errori), ma:

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

Ecco la definizione della errorfunzione della ArgumentParserclasse:

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

. Come vedi, dopo la firma, ci vogliono due argomenti. Tuttavia, le funzioni al di fuori della classe non sanno nulla del primo argomento: selfperché, in termini approssimativi, questo è un parametro per la classe. (Lo so, che tu sai ...) In tal modo, basta passare proprio selfe messagein _error(...)non può (

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

    sys.exit(-1)

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

    parser.error = _error
    ...
...

produrrà:

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

). Puoi passare parser( self) in _errorfunzione, chiamandolo:

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

    sys.exit(-1)

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

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

, ma non vuoi uscire dal programma, in questo momento. Quindi restituirlo:

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

        sys.exit(-1)

    return wrapper
...

. Tuttavia, parsernon sa che è stato modificato, quindi quando si verifica un errore, ne invierà la causa (a proposito, la sua traduzione localizzata). Bene, allora intercettalo:

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

        sys.exit(-1)

    return wrapper
...

. Ora, quando si verifica un errore e parserne invierai la causa, lo intercetterai, guarderai questo e ... lo eliminerai.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.