Je pensais que nargs='*'
Était suffisant pour gérer un nombre variable d'arguments. Apparemment, ce n’est pas le cas et je ne comprends pas la cause de cette erreur.
Le code:
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())
Je pense que l'espace de noms résultant devrait être Namespace(pos='1', foo='2', spam='8', vars=['8', '9'])
. Au lieu de cela, argparse donne cette erreur:
usage: prog.py [-h] [--spam SPAM] pos foo [vars [vars ...]]
error: unrecognized arguments: 9 8
Fondamentalement, argparse ne sait pas où mettre ces arguments supplémentaires ... Pourquoi est-ce?
Le bogue pertinent Python est numéro 15112 .
argparse: nargs='*'
l'argument positionnel n'accepte aucun élément s'il est précédé d'une option et d'un autre
Quand argparse analyse ['1', '2', '--spam', '8', '8', '9']
_ il essaie d’abord de faire correspondre ['1','2']
avec autant d’arguments de position que possible. Avec vos arguments, la chaîne de correspondance de modèle est AAA*
: 1 argument pour pos
et foo
, et zéro argument pour vars
(rappelez-vous *
signifie ZERO_OR_MORE).
['--spam','8']
sont gérés par votre --spam
argument. Puisque vars
a déjà été défini sur []
, il n'y a plus rien à gérer ['8','9']
.
Le changement de programmation en argparse
vérifie le cas où 0
Les chaînes d'arguments satisfont le modèle, mais il reste encore optionals
à analyser. Il diffère ensuite le traitement de cette *
argument.
Vous pourrez peut-être contourner cela en analysant d'abord l'entrée avec parse_known_args
, puis gestion de remainder
avec un autre appel à parse_args
.
Pour avoir une totale liberté dans l’interpolation des options entre positions, dans numéro 14191 , je propose d’utiliser parse_known_args
avec seulement le optionals
, suivi d'un parse_args
qui ne connaît que les positions. Le parse_intermixed_args
La fonction que j’y ai postée pourrait être implémentée dans une sous-classe ArgumentParser
, sans modifier le argparse.py
le code lui-même.
Voici un moyen de gérer les sous-pêcheurs. J'ai pris le parse_known_intermixed_args
fonction, la simplifie pour la présentation, puis en fait la parse_known_args
fonction d’une sous-classe d’analyseur. J'ai dû faire un pas supplémentaire pour éviter la récursivité.
Finalement j'ai changé le _parser_class
de l’action subparsers, chaque sous-fournisseur utilise donc cette alternative parse_known_args
. Une alternative serait de sous-classe _SubParsersAction
, en modifiant éventuellement son __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')
Solution simple: spécifiez le --spam
drapeau avant de spécifier pos
et 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())
La même chose fonctionne si vous placez le --spam
drapeau après avoir spécifié vos arguments 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())
EDIT: Pour ce que cela vaut, il semble que changer le *
à un +
corrigera également l'erreur.
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())