Solución:
El error de Python relevante es el problema 15112.
argparse: nargs="*"
El argumento posicional no acepta ningún elemento si está precedido por una opción y otro posicional
Cuando argparse analiza ['1', '2', '--spam', '8', '8', '9']
primero intenta coincidir ['1','2']
con tantos argumentos posicionales como sea posible. Con sus argumentos, la cadena de coincidencia de patrones es AAA*
: 1 argumento cada uno para pos
y foo
y cero argumentos para vars
(recordar *
significa ZERO_OR_MORE).
['--spam','8']
son manejados por su --spam
argumento. Ya que vars
ya se ha configurado en []
, no queda nada por manejar ['8','9']
.
La programación cambia a argparse
comprueba el caso donde 0
cadenas de argumentos satisfacen el patrón, pero todavía hay optionals
para ser analizado. Luego pospone el manejo de ese *
argumento.
Es posible que pueda solucionar esto analizando primero la entrada con parse_known_args
, y luego manejando el remainder
con otra llamada a parse_args
.
Para tener total libertad en intercalar opcionales entre posicionales, en el número 14191, propongo usar parse_known_args
con solo el optionals
, seguido de un parse_args
que solo sabe de los posicionales. los parse_intermixed_args
función que publiqué allí podría implementarse en una ArgumentParser
subclase, sin modificar el argparse.py
código en sí.
Esta es una forma de manejar subanálisis. He tomado el parse_known_intermixed_args
función, lo simplificó por motivos de presentación, y luego lo convirtió en el parse_known_args
función de una subclase del analizador. Tuve que dar un paso más para evitar la recursividad.
Finalmente cambié el _parser_class
de los sub analizadores Action, por lo que cada sub analizador utiliza esta alternativa parse_known_args
. Una alternativa sería la subclase _SubParsersAction
, posiblemente modificando su __call__
.
from argparse import ArgumentParser
def parse_known_intermixed_args(self, args=None, namespace=None):
# self - argparse parser
# simplified from http://bugs.python.org/file30204/test_intermixed.py
parsefn = super(SubParser, self).parse_known_args # avoid recursion
positionals = self._get_positional_actions()
for action in positionals:
# deactivate positionals
action.save_nargs = action.nargs
action.nargs = 0
namespace, remaining_args = parsefn(args, namespace)
for action in positionals:
# remove the empty positional values from namespace
if hasattr(namespace, action.dest):
delattr(namespace, action.dest)
for action in positionals:
action.nargs = action.save_nargs
# parse positionals
namespace, extras = parsefn(remaining_args, namespace)
return namespace, extras
class SubParser(ArgumentParser):
parse_known_args = parse_known_intermixed_args
parser = ArgumentParser()
parser.add_argument('foo')
sp = parser.add_subparsers(dest="cmd")
sp._parser_class = SubParser # use different parser class for subparsers
spp1 = sp.add_parser('cmd1')
spp1.add_argument('-x')
spp1.add_argument('bar')
spp1.add_argument('vars',nargs="*")
print parser.parse_args('foo cmd1 bar -x one 8 9'.split())
# Namespace(bar="bar", cmd='cmd1', foo='foo', vars=['8', '9'], x='one')
Para quien no sepa lo que es nargs
:
nargs
representa Number Of Arguments
-
3
: 3 valores, puede ser cualquier número que desee -
?
: un valor único, que puede ser opcional -
*
: un número flexible de valores, que se recopilarán en una lista -
+
: como *, pero requiere al menos un valor -
argparse.REMAINDER
: todos los valores que quedan en la línea de comando
Ejemplo:
Pitón
import argparse
my_parser = argparse.ArgumentParser()
my_parser.add_argument('--input', action='store', type=int, nargs=3)
args = my_parser.parse_args()
print(args.input)
Consola
$ python nargs_example.py --input 42
usage: nargs_example.py [-h] [--input INPUT INPUT INPUT]
nargs_example.py: error: argument --input: expected 3 arguments
$ python nargs_example.py --input 42 42 42
[42, 42, 42]
Ver más
Solución simple: especifique el --spam
bandera antes de especificar pos
y foo
:
p = argparse.ArgumentParser()
p.add_argument('pos')
p.add_argument('foo')
p.add_argument('--spam', default=24, type=int, dest="spam")
p.add_argument('vars', nargs="*")
p.parse_args('--spam 8 1 2 8 9'.split())
Lo mismo funciona si coloca el --spam
bandera después de especificar sus argumentos variables.
p = argparse.ArgumentParser()
p.add_argument('pos')
p.add_argument('foo')
p.add_argument('--spam', default=24, type=int, dest="spam")
p.add_argument('vars', nargs="*")
p.parse_args('1 2 8 9 --spam 8'.split())
EDITAR: Por lo que vale, parece que cambiar el *
a un +
también solucionará el error.
p = argparse.ArgumentParser()
p.add_argument('pos')
p.add_argument('foo')
p.add_argument('--spam', default=24, type=int, dest="spam")
p.add_argument('vars', nargs="+")
p.parse_args('1 2 --spam 8 8 9'.split())