Python argparse grupo exclusivo mutuo

88

Lo que necesito es:

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

Intenté esto pero no funciona. podria alguien ayudarme?

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")

¡Gracias!

Sean
fuente
Conectando, pero quería mencionar mi biblioteca joffrey . Le permite hacer lo que quiere esta pregunta, por ejemplo, sin tener que usar subcomandos (como en la respuesta aceptada) o validar todo usted mismo (como en la segunda respuesta más votada).
hola

Respuestas:

109

add_mutually_exclusive_groupno hace que todo un grupo se excluya mutuamente. Hace que las opciones dentro del grupo sean mutuamente excluyentes.

Lo que está buscando son subcomandos . En lugar de prog [-a xxxx | [-b yyy -c zzz]], tendrías:

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

Para invocar con el primer conjunto de argumentos:

prog command_1 -a xxxx

Para invocar con el segundo conjunto de argumentos:

prog command_2 -b yyyy -c zzzz

También puede establecer los argumentos del subcomando como posicionales.

prog command_1 xxxx

Algo así como git o svn:

git commit -am
git merge develop

Ejemplo de trabajo

# 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')

Pruébalo

>>> 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

Buena suerte.

Jonathan
fuente
Ya los he incluido en un grupo de discusión. ¿Cómo puedo agregar un subcomando en este caso? ¡Gracias!
Sean
1
Actualizado con código de muestra. No utilizará grupos, sino subanálisis.
Jonathan
6
Pero, ¿cómo harías lo que OP pidió originalmente? Actualmente tengo un conjunto de subcomandos, pero uno de esos subcomandos necesita la capacidad de elegir entre[[-a <val>] | [-b <val1> -c <val2>]]
code_dredd
2
Esto no responde a la pregunta porque no le permite hacer comandos "sin nombre" y lograr lo que OP pidió[-a xxx | [-b yyy -c zzz]]
El Padrino
34

Si bien la respuesta de Jonathan está perfectamente bien para opciones complejas, existe una solución muy simple que funcionará para los casos simples, por ejemplo, 1 opción excluye otras 2 opciones como en

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

o incluso como en la pregunta original:

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

Así es como lo haría:

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)

Estoy usando aquí las opciones dadas a un contenedor de línea de comando para consultar un mongodb. La collectioninstancia puede llamar al método aggregateo al método findcon argumentos opcionales queryy fields, por lo tanto, puede ver por qué los dos primeros argumentos son compatibles y el último no.

Así que ahora corro parser.parse_args()y verifico su contenido:

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)

Por supuesto, este pequeño truco solo funciona para casos simples y sería una pesadilla verificar todas las opciones posibles si tiene muchas opciones y grupos mutuamente excluyentes. En ese caso, debe dividir sus opciones en grupos de comando como sugirió Jonathan.

Oz123
fuente
5
No llamaría a esto un 'truco' en este caso, ya que parece más legible y manejable, ¡gracias por señalarlo!
sabio
13
Una forma aún mejor sería usar parser.error("-a and -q ..."). De esta forma, la ayuda de uso completa se imprimirá automáticamente.
WGH
Tenga en cuenta que en este caso también deberá validar los casos como: (1) ambos qy fson obligatorios en el primer grupo es el usuario, (2) se requiere cualquiera de los grupos. Y esto hace que la solución "simple" ya no sea tan simple. Así que estaría de acuerdo en que esto es más un truco para un guión hecho a mano, pero no una solución real
El Padrino
4

Hay un parche de Python (en desarrollo) que le permitiría hacer esto.
http://bugs.python.org/issue10984

La idea es permitir la superposición de grupos mutuamente excluyentes. Entonces usagepodría verse así:

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

Cambiar el código argparse para poder crear dos grupos como este fue la parte fácil. Cambiar el usagecódigo de formato requería escribir un archivo personalizado HelpFormatter.

En argparse, los grupos de acción no afectan el análisis. Son solo una helpherramienta de formato. En el help, los grupos mutuamente excluyentes solo afectan la usagelínea. Al analizar, parserutiliza los grupos mutuamente excluyentes para construir un diccionario de conflictos potenciales ( ano puede ocurrir con bo c, bno puede ocurrir con a, etc.), y luego genera un error si surge un conflicto.

Sin ese parche de argparse, creo que su mejor opción es probar el espacio de nombres producido por parse_argsusted mismo (por ejemplo, si ambos ay btienen valores no predeterminados) y generar su propio error. Incluso podría utilizar el propio mecanismo de error del analizador.

parser.error('custom error message')
hpaulj
fuente
1
Problema de Python: bugs.python.org/issue11588 está explorando formas de permitirle escribir pruebas exclusivas / inclusivas personalizadas.
hpaulj