Argparse: ¿Argumentos obligatorios enumerados en "argumentos opcionales"?

229

Utilizo el siguiente código simple para analizar algunos argumentos; Tenga en cuenta que uno de ellos es obligatorio. Desafortunadamente, cuando el usuario ejecuta el script sin proporcionar el argumento, el texto de uso / ayuda que se muestra no indica que haya un argumento no opcional, lo que me parece muy confuso. ¿Cómo puedo hacer que Python indique que un argumento no es opcional?

Aquí está el código:

import argparse
if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description='Foo')
    parser.add_argument('-i','--input', help='Input file name', required=True)
    parser.add_argument('-o','--output', help='Output file name', default="stdout")
    args = parser.parse_args()
    print ("Input file: %s" % args.input )
    print ("Output file: %s" % args.output )

Cuando ejecuto el código anterior sin proporcionar el argumento requerido, obtengo el siguiente resultado:

usage: foo.py [-h] -i INPUT [-o OUTPUT]

Foo

optional arguments:
    -h, --help            show this help message and exit
    -i INPUT, --input INPUT
                          Input file name
    -o OUTPUT, --output OUTPUT
                          Output file name
mort
fuente
55
En la línea de uso, la -i INPUTparte no está rodeada por corchetes, lo que indica que es necesario. Además, puede explicarlo manualmente a través del helpparámetro
Jaime RGP
77
@JaimeRGP Sí, pero eso no es suficiente, por supuesto, y también es menos que prominente. El nombre del grupo asignado optional argumentspara los argumentos requeridos sigue siendo engañoso.
Acumenus

Respuestas:

316

Los parámetros que comienzan con -o --generalmente se consideran opcionales. Todos los demás parámetros son parámetros posicionales y, como tales, son requeridos por el diseño (como los argumentos de la función posicional). Es posible requerir argumentos opcionales, pero esto va en contra de su diseño. Dado que todavía son parte de los argumentos no posicionales, se enumerarán bajo el encabezado confuso "argumentos opcionales", incluso si son necesarios. Sin embargo, los corchetes faltantes en la parte de uso muestran que, de hecho, son obligatorios.

Ver también la documentación :

En general, el módulo argparse supone que las banderas como -f y --bar indican argumentos opcionales, que siempre se pueden omitir en la línea de comando.

Nota: Las opciones obligatorias generalmente se consideran malas porque los usuarios esperan que las opciones sean opcionales y, por lo tanto, deben evitarse cuando sea posible.

Dicho esto, los encabezados "argumentos posicionales" y "argumentos opcionales" en la ayuda son generados por dos grupos de argumentos en los que los argumentos se separan automáticamente. Ahora, podría "hackearlo" y cambiar el nombre de los opcionales, pero una solución mucho más elegante sería crear otro grupo para "argumentos con nombre requeridos" (o como quiera llamarlos):

parser = argparse.ArgumentParser(description='Foo')
parser.add_argument('-o', '--output', help='Output file name', default='stdout')
requiredNamed = parser.add_argument_group('required named arguments')
requiredNamed.add_argument('-i', '--input', help='Input file name', required=True)
parser.parse_args(['-h'])
usage: [-h] [-o OUTPUT] -i INPUT

Foo

optional arguments:
  -h, --help            show this help message and exit
  -o OUTPUT, --output OUTPUT
                        Output file name

required named arguments:
  -i INPUT, --input INPUT
                        Input file name
dar un toque
fuente
He estado teniendo el mismo problema. Intenté tu solución. Agrega los argumentos al nuevo grupo, pero mi código no parece funcionar después de eso. Cualquier solución sería apreciada. Enlace a mi código - pastebin.com/PvC2aujz
Zarar Mahmud
1
@ZararMahmud: está pasando argumentos vacíos en la línea 24 de su código: en su parser.parse_args([])lugar, use parser.parse_args()sin argumentos para capturar el contenido de sys.argv. Por argumento
Devin
@poke: ¡Buena solución! Pero esto no ayuda en caso de que necesite grupos exclusivos mutuos, ¿o me estoy perdiendo algo?
Juez
@Judge, recomendaría leer este pymotw.com/3/argparse/#mutually-exclusive-options
Peter Moore
79

Como prefiero enumerar los argumentos requeridos antes que los opcionales, lo pirateo a través de:

    parser = argparse.ArgumentParser()
    parser._action_groups.pop()
    required = parser.add_argument_group('required arguments')
    optional = parser.add_argument_group('optional arguments')
    required.add_argument('--required_arg', required=True)
    optional.add_argument('--optional_arg')
    return parser.parse_args()

y esto produce:

usage: main.py [-h] [--required_arg REQUIRED_ARG]
               [--optional_arg OPTIONAL_ARG]

required arguments:
  --required_arg REQUIRED_ARG

optional arguments:
  --optional_arg OPTIONAL_ARG

Puedo vivir sin 'ayuda' apareciendo en el grupo de argumentos opcionales.

Karl Rosaen
fuente
3
¿Esto realmente obliga a argparse a tratar cualquiera de los argumentos según sea necesario?
Anthony
66
Creo que el argumento 'requerido' todavía necesita establecerse al agregar un argumento.
Karl Rosaen
Eso es muy agradable.
Paul Cezanne
77
@Anthony: no, necesitas el 'required = True' en add_argument para eso. La respuesta anterior solo ilustra la agrupación de argumentos.
usuario2275693
47

Edificio fuera de @Karl Rosaen

parser = argparse.ArgumentParser()
optional = parser._action_groups.pop() # Edited this line
required = parser.add_argument_group('required arguments')
# remove this line: optional = parser...
required.add_argument('--required_arg', required=True)
optional.add_argument('--optional_arg')
parser._action_groups.append(optional) # added this line
return parser.parse_args()

y esto produce:

usage: main.py [-h] [--required_arg REQUIRED_ARG]
           [--optional_arg OPTIONAL_ARG]

required arguments:
  --required_arg REQUIRED_ARG

optional arguments:
  -h, --help                    show this help message and exit
  --optional_arg OPTIONAL_ARG
RalphyZ
fuente
1
Por cierto, ¿hay alguna forma (métodos) de cómo obtener acceso _action_groupsin acceder al miembro protegido? En mi caso, necesito agregar algún argumento al grupo ya existente (personalizado).
machin
Esto es genial. Resuelve el elemento --help que aparece en una segunda lista opcional.
Jeremy
Nota : esta respuesta rompe la API expuesta, verifique la respuesta de Bryan_D a continuación.
lol
18

Una vez más, construyendo a partir de @RalphyZ

Este no rompe la API expuesta.

from argparse import ArgumentParser, SUPPRESS
# Disable default help
parser = ArgumentParser(add_help=False)
required = parser.add_argument_group('required arguments')
optional = parser.add_argument_group('optional arguments')

# Add back help 
optional.add_argument(
    '-h',
    '--help',
    action='help',
    default=SUPPRESS,
    help='show this help message and exit'
)
required.add_argument('--required_arg', required=True)
optional.add_argument('--optional_arg')

Que mostrará lo mismo que arriba y debería sobrevivir a futuras versiones:

usage: main.py [-h] [--required_arg REQUIRED_ARG]
           [--optional_arg OPTIONAL_ARG]

required arguments:
  --required_arg REQUIRED_ARG

optional arguments:
  -h, --help                    show this help message and exit
  --optional_arg OPTIONAL_ARG
Bryan_D
fuente
¿Puedes explicar cómo la respuesta de RalphyZ rompe la API expuesta?
jeremysprofile
55
_action_groupsestá destinado solo para uso interno. Por lo tanto, no hay garantía de compatibilidad entre versiones.
Bryan_D