La solución proporcionada por @Vikas falla para argumentos opcionales específicos de subcomando, pero el enfoque es válido. Aquí hay una versión mejorada:
import argparse
parser = argparse.ArgumentParser(prog='PROG')
parser.add_argument('--foo', action='store_true', help='foo help')
subparsers = parser.add_subparsers(help='sub-command help', dest='subparser_name')
parser_a = subparsers.add_parser('command_a', help='command_a help')
parser_a.add_argument('bar', type=int, help='bar help')
parser_b = subparsers.add_parser('command_b', help='command_b help')
parser_b.add_argument('--baz', choices='XYZ', help='baz help')
argv = ['--foo', 'command_a', '12', 'command_b', '--baz', 'Z']
while argv:
print(argv)
options, argv = parser.parse_known_args(argv)
print(options)
if not options.subparser_name:
break
Esto usa en parse_known_args
lugar de parse_args
.parse_args
aborta tan pronto como se encuentra un argumento desconocido para el sub analizador actual,parse_known_args
devuelve como un segundo valor en la tupla devuelta. En este enfoque, los argumentos restantes se alimentan nuevamente al analizador. Entonces, para cada comando, se crea un nuevo espacio de nombres.
Tenga en cuenta que en este ejemplo básico, todas las opciones globales se agregan solo al primer espacio de nombres de opciones, no a los espacios de nombre posteriores.
Este enfoque funciona bien para la mayoría de las situaciones, pero tiene tres limitaciones importantes:
- No es posible usar el mismo argumento opcional para diferentes subcomandos, como
myprog.py command_a --foo=bar command_b --foo=bar
.
- No es posible utilizar argumentos posicionales de longitud variable con subcomandos (
nargs='?'
o nargs='+'
o nargs='*'
).
- Se analiza cualquier argumento conocido, sin "romper" en el nuevo comando. Por ejemplo,
PROG --foo command_b command_a --baz Z 12
con el código anterior, --baz Z
será consumido por command_b
, no por command_a
.
Estas limitaciones son una limitación directa de argparse. Aquí hay un ejemplo simple que muestra las limitaciones de argparse, incluso cuando se usa un solo subcomando:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('spam', nargs='?')
subparsers = parser.add_subparsers(help='sub-command help', dest='subparser_name')
parser_a = subparsers.add_parser('command_a', help='command_a help')
parser_a.add_argument('bar', type=int, help='bar help')
parser_b = subparsers.add_parser('command_b', help='command_b help')
options = parser.parse_args('command_a 42'.split())
print(options)
Esto aumentará el error: argument subparser_name: invalid choice: '42' (choose from 'command_a', 'command_b')
.
La causa es que el método interno argparse.ArgParser._parse_known_args()
es demasiado codicioso y asume que ese command_a
es el valor del spam
argumento opcional . En particular, al 'dividir' los argumentos opcionales y posicionales, _parse_known_args()
no se fija en los nombres de los argumentos (como command_a
o command_b
), sino simplemente en el lugar donde aparecen en la lista de argumentos. También asume que cualquier subcomando consumirá todos los argumentos restantes. Esta limitación argparse
también impide una implementación adecuada de subanálisis de múltiples comandos. Desafortunadamente, esto significa que una implementación adecuada requiere una reescritura completa del argparse.ArgParser._parse_known_args()
método, que son más de 200 líneas de código.
Dadas estas limitaciones, puede ser una opción simplemente volver a un único argumento de opción múltiple en lugar de subcomandos:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--bar', type=int, help='bar help')
parser.add_argument('commands', nargs='*', metavar='COMMAND',
choices=['command_a', 'command_b'])
options = parser.parse_args('--bar 2 command_a command_b'.split())
print(options)
Incluso es posible enumerar los diferentes comandos en la información de uso, consulte mi respuesta https://stackoverflow.com/a/49999185/428542