Muestra el mensaje de ayuda con el argumento de python cuando se llama al script sin ningún argumento

226

Esto podría ser simple. Supongamos que tengo un programa que usa argparse para procesar argumentos / opciones de línea de comandos. Lo siguiente imprimirá el mensaje de 'ayuda':

./myprogram -h

o:

./myprogram --help

Pero, si ejecuto el script sin ningún argumento, no hace nada. Lo que quiero que haga es mostrar el mensaje de uso cuando se llama sin argumentos. ¿Cómo se hace eso?

musashiXXX
fuente

Respuestas:

273

Esta respuesta proviene de Steven Bethard en los grupos de Google . Lo vuelvo a publicar aquí para facilitar el acceso de las personas sin una cuenta de Google.

Puede anular el comportamiento predeterminado del errormétodo:

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

Tenga en cuenta que la solución anterior imprimirá el mensaje de ayuda cada vez que se active el error método. Por ejemplo, test.py --blahtambién imprimirá el mensaje de ayuda si --blahno es una opción válida.

Si desea imprimir el mensaje de ayuda solo si no se proporcionan argumentos en la línea de comando, entonces quizás esta sea la forma más fácil:

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

Tenga en cuenta que parser.print_help()imprime en stdout de forma predeterminada. Como init_js sugiere , use parser.print_help(sys.stderr)para imprimir en stderr.

unutbu
fuente
Sí ... eso es lo que me preguntaba, si había una forma de argparse para manejar este escenario. ¡Gracias!
musashiXXX
66
En la segunda solución que uso parser.print_usage()en lugar de parser.print_help(): el mensaje de ayuda incluye el uso, pero es más detallado.
user2314737
55
Hubiera votado por la segunda parte de la respuesta, pero anular me error()parece una idea terrible. Tiene un propósito diferente, no está diseñado para imprimir un uso amigable o ayuda.
Peterino
@Peterino: la anulación se produce en una clase secundaria, por lo que esto no debería ser un problema. Es explícito.
Marcel Wilson
1
@unutbu MARAVILLOSO! Exactamente lo que necesitaba. Una pregunta, ¿se puede aplicar esto a los subcomandos también? Por lo general, solo obtengo `` Espacio de nombres (salida = Ninguno) `. ¿Cómo puedo activar un error fácilmente en TODOS los subcomandos? Me gustaría activar un error allí.
Jonathan Komar
56

En lugar de escribir una clase, se puede usar un try / except en su lugar

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

Lo bueno es que el flujo de trabajo es más claro y no necesita una clase de código auxiliar. La desventaja es que la primera línea de 'uso' se imprime dos veces.

Esto necesitará al menos un argumento obligatorio. Sin argumentos obligatorios, es válido proporcionar cero argumentos en la línea de comandos.

vacri
fuente
yo también, prefiero esto a la respuesta aceptada. Agregar una clase es excesivo para imprimir ayuda cuando los argumentos son inesperados. Deje que el excelente módulo argparse maneje los casos de error por usted.
Nicole Finnie el
77
Este código imprime ayuda 2 veces si -hse usa el indicador, y las impresiones innecesarias ayudan si --versionse usa el indicador. Para mitigar esos problemas, puede verificar un tipo de error como este:except SystemExit as err: if err.code == 2: parser.print_help()
pkowalczyk
25

Con argparse podrías hacer:

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
fuente
55
Esto debe venir antes de la llamada aparser.parse_args()
Bob Stein
18

Si tiene argumentos que deben especificarse para que se ejecute el script, use el parámetro requerido para ArgumentParser como se muestra a continuación:

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

parse_args () informará un error si el script se ejecuta sin ningún argumento.

pd321
fuente
2
Esta es la solución más simple y también funcionará con opciones no válidas especificadas.
Steve Scherer
1
Convenido. Creo que siempre es mejor aprovechar las capacidades integradas del analizador de argumentos que escribir un controlador adicional de algún tipo.
Christopher Hunter
18

Si asocia funciones predeterminadas para (sub) analizadores, como se menciona a continuación add_subparsers, simplemente puede agregarlo como la acción predeterminada:

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

Agregue el try, excepto si genera excepciones debido a la falta de argumentos posicionales.

AManOfScience
fuente
1
Esta respuesta está muy subestimada. Simple y funciona muy bien con sub-analizadores.
orodbhen
¡Gran respuesta! El único cambio que hice fue usar una lambda sin parámetro.
boh717
12

La solución más limpia será pasar manualmente el argumento predeterminado si no se dio ninguno en la línea de comando:

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

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

Esto imprimirá ayuda completa (no uso corto) si se llama sin argumentos.

Ievgen Popovych
fuente
2
sys.argv[1:]Es un idioma muy común. Veo parser.parse_args(None if sys.argv[1:] else ['-h'])más idiomático y más limpio.
Nuno André
1
@ NunoAndré gracias - actualizó la respuesta. Se siente más pitónico de hecho.
Ievgen Popovych
10

Lanzando mi versión a la pila aquí:

import argparse

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

Puede notar el parser.exit- Principalmente lo hago así porque guarda una línea de importación si esa era la única razón sysen el archivo ...

pauricthelodger
fuente
parser.exit (1) es bueno! Buena adición
cgseller
44
Lamentablemente, parser.parse_args () se cerrará si falta un argumento posicional. Entonces esto solo funciona cuando se usan argumentos opcionales.
Marcel Wilson
1
@MarcelWilson, de hecho, ¡buena captura! Pensaré en cómo cambiarlo.
pauricthelodger
not vars(args)puede no funcionar cuando los argumentos tienen defaultmétodo.
funkid
5

Hay un par de frases con sys.argv[1:](un lenguaje muy común de Python para referirse a los argumentos de la línea de comandos, que es sys.argv[0]el nombre del script) que pueden hacer el trabajo.

El primero se explica por sí mismo, es limpio y pitónico:

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

El segundo es un poco más pirateado. Combinando el hecho evaluado previamente de que hay una lista vacía Falsecon las equivalencias True == 1y False == 0obtienes esto:

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

Quizás demasiados corchetes, pero bastante claro si se realizó una selección de argumento anterior.

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

El parser.exitmétodo también acepta un status(código de retorno) y un messagevalor (¡incluya una nueva línea final usted mismo!).

un ejemplo obstinado :)

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

Llamadas de ejemplo:

$ python3 ~ / helloworld.py; echo $?
uso: helloworld.py [-h] [--ejemplo]

 Ejemplo de archivo Python basado en argparser

argumentos opcionales:
  -h, - ayuda a mostrar este mensaje de ayuda y salir
  --ejemplo Ejemplo de argumento

Simplemente no sé qué salió mal, tal vez falta: ¿condición de ejemplo?
128
$ python3 ~ / helloworld.py --ejemplo; echo $?
0 0
ThorSummoner
fuente
0

Establezca sus argumentos posicionales con nargs y verifique si los argumentos posicionales están vacíos.

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

Nargs de referencia de Python

zerocog
fuente
0

Aquí hay otra forma de hacerlo, si necesita algo flexible donde desea mostrar ayuda si se pasan parámetros específicos, ninguno en absoluto o más de 1 argumento en conflicto:

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

¡Salud!

radtek
fuente
Creo que sería mucho más fácil usar subparsers o mutually_exclusive_group
Tim Bray
0

Si su comando es algo donde un usuario necesita elegir alguna acción, entonces use un grupo mutuamente exclusivo con required = True .

Esta es una especie de extensión de la respuesta dada por 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')

Salida:

$ python3 a_test.py
uso: a_test.py [-h] (--batch pay_id | --list |
--all ) a_test.py: error: uno de los argumentos --batch --list --todos son obligatorios

Esto solo da la ayuda básica. Y algunas de las otras respuestas le brindarán toda la ayuda. Pero al menos tus usuarios saben que pueden hacer -h

Tim Bray
fuente
0

Esto no es bueno (también, porque intercepta todos los errores), pero:

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

Aquí está la definición de la errorfunción de la ArgumentParserclase:

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

. Como puede ver, después de la firma, se necesitan dos argumentos. Sin embargo, las funciones fuera de la clase no saben nada sobre el primer argumento: selfporque, en términos generales, este es un parámetro para la clase. (Lo sé, que usted sabe ...) Por lo tanto, sólo tiene que pasar propia selfy messageen _error(...)no puede (

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

    sys.exit(-1)

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

    parser.error = _error
    ...
...

dará salida:

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

) Puede pasar parser( self) en _errorfunción, llamándolo:

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

    sys.exit(-1)

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

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

, pero no quieres salir del programa, ahora mismo. Luego devuélvelo:

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

        sys.exit(-1)

    return wrapper
...

. Sin embargo, parserno sabe que se ha modificado, por lo tanto, cuando se produce un error, enviará la causa (por cierto, su traducción localizada). Bueno, entonces interceptarlo:

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

        sys.exit(-1)

    return wrapper
...

. Ahora, cuando ocurre un error y parserenviará la causa del mismo, lo interceptarás, mirarás esto y ... lo arrojarás.

Maxim Temny
fuente