Comment analyser les multiples imbriquée des sous-commandes à l'aide de python argparse?
Je me suis mise en œuvre d'un programme en ligne de commande qui a une interface comme ceci:
cmd [GLOBAL_OPTIONS] {command [COMMAND_OPTS]} [{command [COMMAND_OPTS]} ...]
Je suis passé par le argparse documentation. Je peux mettre en œuvre GLOBAL_OPTIONS
comme argument optionnel à l'aide de add_argument
dans argparse
. Et le {command [COMMAND_OPTS]}
à l'aide de Sous-commandes.
De la documentation, il semble que je ne peut avoir qu'un sous-commande. Mais comme vous pouvez le voir, j'ai à mettre en œuvre un ou plusieurs sous-commandes. Quel est le meilleur moyen pour analyser de tels arguments de ligne de commande utilisant les argparse
?
- Je ne pense pas que c'est ce que sous-commandes est prévu pour. À partir de la documentation, il déclare que c'est, en substance, pour le contrôle distinct sous-programmes. Avez-vous regardé dans les argument groupes?
- distutils
./setup.py
a aussi ce style d'interface CLI, serait intéressant de regarder dans le code source.
Vous devez vous connecter pour publier un commentaire.
@mgilson a une belle réponse à cette question. Mais le problème avec le fractionnement du sys.argv moi, c'est que je perds tout ce qui est beau message d'aide Argparse génère pour l'utilisateur. J'ai donc fini par le faire:
Maintenant, après la première analyser tous enchaînés les commandes sont stockées dans
extra
. J'ai d'analyse, alors qu'il n'est pas vide pour obtenir toutes les commandes chaînées et de créer des espaces de noms pour eux. Et je reçois de plus agréable l'utilisation de la chaîne de argparse génère.parse_extra()
?namespace
de l'analyseur en appelantnamespace = argparser.parse_args()
, j'appelleparse_extra
avecparser
etnamespace
.extra_namespaces = parse_extra( argparser, namespace )
parser
dans le code que vous avez. Je ne vois qu'il est utilisé pour ajouter de laextra
argument. Puis vous avez mentionné de nouveau dans le commentaire ci-dessus. Est-il censé êtreargparser
?argparser
. Va le modifier.parser_b = subparsers.add_parser('command_b', help='command_b help')
;parser_b.add_argument('--baz', choices='XYZ', help='baz help')
;options = argparser.parse_args(['--foo', 'command_a', 'command_b', '--baz', 'Z'])
; Cela échoue avec une erreurPROG: error: unrecognized arguments: --baz Z
. La raison en est que lors de l'analyse decommand_a
, les arguments facultatifs decommand_b
sont déjà analysé (et sont inconnus pour le subparser decommand_a
).Je suis venu avec la même qustion, et il semble que j'ai obtenu une meilleure réponse.
La solution est on ne doit pas simplement nid subparser avec un autre subparser, mais nous pouvons ajouter subparser suivantes avec un analyseur de suivre un autre subparser.
Code vous dire comment:
argparse
ne permettent imbriquée subparsers. Mais je ne l'ai vu utilisé dans un autre lieu - dans un cas de test pour un Python problème, bugs.python.org/issue14365parse_known_args
renvoie à un espace de Noms et une liste inconnu à cordes. Ceci est similaire à laextra
dans les bagages de réponse.produit:
Une alternative boucle à chaque subparser son propre espace de noms. Cela permet de chevauchement dans positionals noms.
rest = '--foo 0 cmd2 --foo2 2 --bar cmd3 --foo3 3 cmd1 --foo1 1'.split()
), puis argparse prendra fin enerror: too few arguments
au lieu de pointer l'option non valide. C'est parce que la mauvaise option sera laissé dansrest
jusqu'à ce que nous sommes à court d'arguments de la commande.# or sys.argv
devrait être# or sys.argv[1:]
.Vous pouvez essayer de arghandler. C'est une extension de argparse avec le soutien explicite pour les sous-commandes.
Vous pouvez toujours réparties de la ligne de commande vous-même (split
sys.argv
sur vos noms de commande), et ensuite seulement passer la partie correspondant à la commande en particulier àparse_args
-- Vous pouvez même utiliser la mêmeNamespace
en utilisant le mot-clé namespace si vous le souhaitez.Regroupement de la ligne de commande est facile avec
itertools.groupby
:non testé
itertools.groupby()
! Ceci c'est comment j'ai fait la même chose avant que je savais à propos degroupby()
.L'amélioration de la réponse par @mgilson, j'ai écrit une petite analyse de la méthode qui divise argv en parties et met les valeurs des arguments de commandes dans la hiérarchie des espaces de noms:
Il se comporte correctement, en fournissant de nice argparse aider:
Pour
./test.py --help
:Pour
./test.py cmd1 --help
:Et crée une hiérarchie d'espaces de noms contenant les valeurs de l'argument:
split_argv[0]
qui est en fait vide danssplit_argv
, parce que vous ajoutez[c]
àsplit_argv
(d'abord mis à[[]]
). Si vous modifiez la ligne 7 àsplit_argv = []
, tout fonctionne comme prévu.subparser
a été utilisé par l'ajout de dest à laadd_subparsers
méthode stackoverflow.com/questions/8250010/...La solution par @Vikas échoue pour la commande spécifique des arguments optionnels, mais l'approche est valide. Ici est une version améliorée:
Il utilise
parse_known_args
au lieu deparse_args
.parse_args
abandonne dès qu'un argument inconnu à l'actuel subparser est rencontré,parse_known_args
retourne sous la forme d'une deuxième valeur dans le retour de tuple. Dans cette approche, les arguments restants sont nourris de nouveau à l'analyseur. Donc, pour chaque commande, un nouvel espace de Noms est créé.Notez que dans cet exemple de base, toutes les options globales sont ajoutés à la première des options de l'espace de Noms seulement, pas pour la suite des espaces de noms.
Cette approche fonctionne bien pour la plupart des situations, mais a trois limites importantes:
myprog.py command_a --foo=bar command_b --foo=bar
.nargs='?'
ounargs='+'
ounargs='*'
).PROG --foo command_b command_a --baz Z 12
avec le code ci-dessus,--baz Z
sera consommée parcommand_b
, pas parcommand_a
.Ces limitations sont une limitation directe de la argparse. Voici un exemple simple qui montre les limites de argparse -même lors de l'utilisation d'un seul sous-commande-:
Cela contribuera à accroître la
error: argument subparser_name: invalid choice: '42' (choose from 'command_a', 'command_b')
.La cause est que la méthode interne
argparse.ArgParser._parse_known_args()
il est trop gourmand et suppose quecommand_a
est la valeur de l'optionspam
argument. En particulier, lors de la division optionnels et les arguments de position,_parse_known_args()
ne regarde pas les noms des arugments (commecommand_a
oucommand_b
), mais seulement là où ils se produisent dans la liste d'arguments. Il suppose également que toutes les sous-commande consomment tous les autres arguments.Cette limitation de
argparse
empêche aussi une bonne mise en œuvre de la multi-commande subparsers. Cela signifie que malheureusement une bonne mise en œuvre nécessite une réécriture complète de laargparse.ArgParser._parse_known_args()
méthode, qui est de 200 lignes de code.Compte tenu de ces limites, il peut être une des options pour simplement revenir à un seul choix multiples argument au lieu de sous-commandes:
Il est même possible de lister les différentes commandes de l'utilisation d'informations, voir ma réponse https://stackoverflow.com/a/49999185/428542
Un autre paquet qui prend en charge parallèle analyseurs est "declarative_parser".
et l'espace devient:
Disclaimer: je suis l'auteur. Nécessite Python 3.6. Pour installer utiliser:
Ici est la la documentation et voici le repo GitHub.
vous pouvez utiliser le package optparse