Groupe exclusif mutuel Python Argparse

88

Ce dont j'ai besoin c'est:

pro [-a xxx | [-b yyy -c zzz]]

J'ai essayé cela mais ne fonctionne pas. Est-ce que quelqu'un peut me donner un coup de main?

group= parser.add_argument_group('Model 2')
group_ex = group.add_mutually_exclusive_group()
group_ex.add_argument("-a", type=str, action = "store", default = "", help="test")
group_ex_2 = group_ex.add_argument_group("option 2")
group_ex_2.add_argument("-b", type=str, action = "store", default = "", help="test")
group_ex_2.add_argument("-c", type=str, action = "store", default = "", help="test")

Merci!

Sean
la source
Brancher, mais je voulais mentionner ma bibliothèque joffrey . Vous permet de faire ce que cette question veut, par exemple, sans vous obliger à utiliser des sous-commandes (comme dans la réponse acceptée) ou à tout valider vous-même (comme dans la deuxième réponse la plus votée).
bonjour

Réponses:

109

add_mutually_exclusive_groupne rend pas un groupe entier mutuellement exclusif. Il rend les options au sein du groupe mutuellement exclusives.

Ce que vous recherchez, ce sont des sous - commandes . Au lieu de prog [-a xxxx | [-b yyy -c zzz]], vous auriez:

prog 
  command 1 
    -a: ...
  command 2
    -b: ...
    -c: ...

Pour appeler avec le premier ensemble d'arguments:

prog command_1 -a xxxx

Pour appeler avec le deuxième ensemble d'arguments:

prog command_2 -b yyyy -c zzzz

Vous pouvez également définir les arguments de la sous-commande comme positionnels.

prog command_1 xxxx

Un peu comme git ou svn:

git commit -am
git merge develop

Exemple de travail

# create the top-level parser
parser = argparse.ArgumentParser(prog='PROG')
parser.add_argument('--foo', action='store_true', help='help for foo arg.')
subparsers = parser.add_subparsers(help='help for subcommand')

# create the parser for the "command_1" command
parser_a = subparsers.add_parser('command_1', help='command_1 help')
parser_a.add_argument('a', type=str, help='help for bar, positional')

# create the parser for the "command_2" command
parser_b = subparsers.add_parser('command_2', help='help for command_2')
parser_b.add_argument('-b', type=str, help='help for b')
parser_b.add_argument('-c', type=str, action='store', default='', help='test')

Essaye-le

>>> parser.print_help()
usage: PROG [-h] [--foo] {command_1,command_2} ...

positional arguments:
  {command_1,command_2}
                        help for subcommand
    command_1           command_1 help
    command_2           help for command_2

optional arguments:
  -h, --help            show this help message and exit
  --foo                 help for foo arg.
>>>

>>> parser.parse_args(['command_1', 'working'])
Namespace(a='working', foo=False)
>>> parser.parse_args(['command_1', 'wellness', '-b x'])
usage: PROG [-h] [--foo] {command_1,command_2} ...
PROG: error: unrecognized arguments: -b x

Bonne chance.

Jonathan
la source
Je les ai déjà mis sous un groupe d'argumentation. Comment puis-je ajouter une sous-commande dans ce cas? Merci!
Sean
1
Mis à jour avec un exemple de code. Vous n'utiliserez pas de groupes, mais des sous-analyseurs.
Jonathan
6
Mais comment feriez-vous ce qu'OP a demandé à l'origine? J'ai actuellement un ensemble de sous-commandes, mais l'une de ces sous-commandes nécessite la possibilité de choisir entre[[-a <val>] | [-b <val1> -c <val2>]]
code_dredd
2
Cela ne répond pas à la question car cela ne vous permet pas de faire des commandes "non-nom" et d'accomplir ce qu'OP a demandé[-a xxx | [-b yyy -c zzz]]
Le Parrain
34

Alors que la réponse de Jonathan convient parfaitement aux options complexes, il existe une solution très simple qui fonctionnera pour les cas simples, par exemple 1 option exclut 2 autres options comme dans

command [- a xxx | [ -b yyy | -c zzz ]] 

ou même comme dans la question initiale:

pro [-a xxx | [-b yyy -c zzz]]

Voici comment je le ferais:

parser = argparse.ArgumentParser()

# group 1 
parser.add_argument("-q", "--query", help="query", required=False)
parser.add_argument("-f", "--fields", help="field names", required=False)

# group 2 
parser.add_argument("-a", "--aggregation", help="aggregation",
                    required=False)

J'utilise ici les options données à un wrapper de ligne de commande pour interroger un mongodb. L' collectioninstance peut appeler la méthode aggregateou la méthode findavec des arguments optionnels queryet fields, par conséquent, vous voyez pourquoi les deux premiers arguments sont compatibles et le dernier ne l'est pas.

Alors maintenant, je cours parser.parse_args()et vérifie son contenu:

args = parser().parse_args()

print args.aggregation
if args.aggregation and (args.query or args.fields):
    print "-a and -q|-f are mutually exclusive ..."
    sys.exit(2)

Bien sûr, ce petit hack ne fonctionne que pour des cas simples et cela deviendrait un cauchemar de vérifier toutes les options possibles si vous avez de nombreuses options et groupes mutuellement exclusifs. Dans ce cas, vous devriez diviser vos options en groupes de commande comme Jonathan l'a suggéré.

Oz123
la source
5
Je n'appellerais pas cela un `` hack '' pour ce cas, car il semble à la fois plus lisible et gérable - merci de l'avoir signalé!
sage
13
Une manière encore meilleure serait d'utiliser parser.error("-a and -q ..."). De cette façon, l'aide à l'utilisation complète sera imprimée automatiquement.
WGH
Veuillez noter que dans ce cas, vous devrez également valider les cas tels que: (1) les deux qet fsont requis dans le premier groupe est l'utilisateur, (2) l'un ou l'autre des groupes est requis. Et cela rend la solution «simple» plus si simple. Je suis donc d'accord pour dire que c'est plus un hack pour un script artisanal, mais pas une vraie solution
The Godfather
4

Il existe un patch python (en développement) qui vous permettrait de faire cela.
http://bugs.python.org/issue10984

L'idée est de permettre le chevauchement de groupes mutuellement exclusifs. Cela usagepourrait donc ressembler à:

pro [-a xxx | -b yyy] [-a xxx | -c zzz]

Changer le code argparse pour pouvoir créer deux groupes comme celui-ci était la partie la plus facile. La modification du usagecode de formatage a nécessité l'écriture d'une personnalisation HelpFormatter.

Dans argparse, les groupes d'actions n'affectent pas l'analyse. Ils ne sont qu'un helpoutil de formatage. Dans le help, les groupes mutuellement exclusifs affectent uniquement la usageligne. Lors de l'analyse, le parserutilise les groupes mutuellement exclusifs pour construire un dictionnaire de conflits potentiels ( ane peut pas se produire avec bou c, bne peut pas se produire avec a, etc.), puis génère une erreur en cas de conflit.

Sans ce correctif argparse, je pense que votre meilleur choix est de tester l'espace de noms produit par parse_argsvous-même (par exemple si les deux aet bont des valeurs non par défaut), et de déclencher votre propre erreur. Vous pouvez même utiliser le propre mécanisme d'erreur de l'analyseur.

parser.error('custom error message')
hpaulj
la source
1
Problème Python: bugs.python.org/issue11588 explore des moyens de vous permettre d'écrire des tests exclusifs / inclusifs personnalisés.
hpaulj