2011-07-17 22 views
57

He estado usando argparse para un programa de Python que puede -prepare, -upload o ambos:Python argparse: Hacer al menos un argumento requiere

parser = argparse.ArgumentParser(description='Log archiver arguments.') 
parser.add_argument('-process', action='store_true') 
parser.add_argument('-upload', action='store_true') 
args = parser.parse_args() 

El programa no tiene sentido sin al menos un parámetro. ¿Cómo puedo configurar argparse para forzar que se elija al menos un parámetro?

ACTUALIZACIÓN:

raíz de los comentarios: ¿Cuál es la forma Pythonic para parametrizar un programa con al menos una opción?

+8

'-x' es universalmente una bandera y opcional. Corta el '-' si es necesario. – delnan

+0

¿No podría hacer que 'process' sea el comportamiento predeterminado (sin la necesidad de especificar ninguna opción) y permitir que el usuario lo cambie a la opción' upload' si * that * está establecido? Por lo general, las opciones deben ser opcionales, de ahí el nombre. Se deben evitar las opciones requeridas (esto también está en los documentos 'argparse'). –

+0

@ Adamvatan Hace casi tres años que hizo su pregunta, pero me gustó el desafío oculto y usé la ventaja de que había nuevas soluciones disponibles para este tipo de tareas. –

Respuesta

62
if not (args.process or args.upload): 
    parser.error('No action requested, add -process or -upload') 
+0

Esa es probablemente la única forma, si 'argparse' no tiene una opción incorporada para esto. –

+0

Bien hecho, Sr. Hagemeister;) –

16

Si no es el 'o ambos' parte (me he perdido inicialmente esto) se podrían usar algo como esto:

parser = argparse.ArgumentParser(description='Log archiver arguments.') 
parser.add_argument('--process', action='store_const', const='process', dest='mode') 
parser.add_argument('--upload', action='store_const', const='upload', dest='mode') 
args = parser.parse_args() 
if not args.mode: 
    parser.error("One of --process or --upload must be given") 

Aunque, probablemente sería una idea mejor utilizar subcommands lugar.

+3

Creo que quiere permitir '--process' OR' --upload', no XOR. Esto evita que ambas opciones se configuren al mismo tiempo. – phihag

+0

+1 porque mencionó los subcomandos. Sin embargo, como alguien señaló en los comentarios '-x' y' --xxx' son típicamente parámetros opcionales. – mac

+0

@phihag: tienes razón, leí mal la pregunta. –

20
args = vars(parser.parse_args()) 
if not any(args.values()): 
    parser.error('No arguments provided.') 
+2

+1 para una solución generalizada. También me gusta el uso de 'vars()', que también es útil para pasar opciones cuidadosamente nombradas a un constructor con **. – Lenna

+0

que es exactamente lo que estoy haciendo con eso. ¡Gracias! – brentlance

+1

Dang, me gusta ese 'vars'. Acabo de hacer '.__ dict__' y me sentí tonto antes. –

4

Si necesita un programa de Python para funcionar con al menos un parámetro, añadir un argumento que no lo hace tienen la opción de prefijo (- o - por defecto) y establecer nargs=+ (mínimo de un argumento necesario). El problema con este método que encontré es que si no especificas el argumento, argparse generará un error de "muy pocos argumentos" y no imprimirá el menú de ayuda. Si no es necesario que la funcionalidad, aquí está cómo hacerlo en código:

import argparse 

parser = argparse.ArgumentParser(description='Your program description') 
parser.add_argument('command', nargs="+", help='describe what a command is') 
args = parser.parse_args() 

I piensan que cuando se agrega una discusión con los prefijos de opción, nargs gobierna todo el analizador argumento y no sólo la opción . (Lo que quiero decir es, si usted tiene una bandera --option con nargs="+", entonces --option bandera de espera al menos un argumento. Si tiene option con nargs="+", se espera que al menos un argumento general.)

+0

Podrías agregar 'choices = ['process', 'upload']' a ese argumento. – hpaulj

8

Requisitos en cuanto al

  • use argparse (voy a ignorar este)
  • permiten llamar a una o dos acciones (al menos una requerida).
  • intentan por Pythonic (prefiero llamarlo "POSIX" -como)

También hay algunos requisitos implícitos cuando se vive en la línea de comandos:

  • explicaremos el uso para el usuario en de manera que es fácil de entender
  • opciones será opcional
  • permiten especificar banderas y opciones
  • permite combinar con otros parámetros (como fil nombre o nombres).

Solución de muestra usando docopt (archivo managelog.py):

"""Manage logfiles 
Usage: 
    managelog.py [options] process -- <logfile>... 
    managelog.py [options] upload -- <logfile>... 
    managelog.py [options] process upload -- <logfile>... 
    managelog.py -h 

Options: 
    -V, --verbose  Be verbose 
    -U, --user <user> Username 
    -P, --pswd <pswd> Password 

Manage log file by processing and/or uploading it. 
If upload requires authentication, you shall specify <user> and <password> 
""" 
if __name__ == "__main__": 
    from docopt import docopt 
    args = docopt(__doc__) 
    print args 

intento ejecutarlo:

$ python managelog.py 
Usage: 
    managelog.py [options] process -- <logfile>... 
    managelog.py [options] upload -- <logfile>... 
    managelog.py [options] process upload -- <logfile>... 
    managelog.py -h 

Muestra la ayuda:

$ python managelog.py -h 
Manage logfiles 
Usage: 
    managelog.py [options] process -- <logfile>... 
    managelog.py [options] upload -- <logfile>... 
    managelog.py [options] process upload -- <logfile>... 
    managelog.py -h 

Options: 
    -V, --verbose  Be verbose 
    -U, --user <user> Username 
    -P, --pswd <pswd> P managelog.py [options] upload -- <logfile>... 

Manage log file by processing and/or uploading it. 
If upload requires authentication, you shall specify <user> and <password> 

y usarlo:

$ python managelog.py -V -U user -P secret upload -- alfa.log beta.log 
{'--': True, 
'--pswd': 'secret', 
'--user': 'user', 
'--verbose': True, 
'-h': False, 
'<logfile>': ['alfa.log', 'beta.log'], 
'process': False, 
'upload': True} 

Corto alternativo short.py

No puede ser variante aún más corto:

"""Manage logfiles 
Usage: 
    short.py [options] (process|upload)... -- <logfile>... 
    short.py -h 

Options: 
    -V, --verbose  Be verbose 
    -U, --user <user> Username 
    -P, --pswd <pswd> Password 

Manage log file by processing and/or uploading it. 
If upload requires authentication, you shall specify <user> and <password> 
""" 
if __name__ == "__main__": 
    from docopt import docopt 
    args = docopt(__doc__) 
    print args 

uso es el siguiente:

$ python short.py -V process upload -- alfa.log beta.log 
{'--': True, 
'--pswd': None, 
'--user': None, 
'--verbose': True, 
'-h': False, 
'<logfile>': ['alfa.log', 'beta.log'], 
'process': 1, 
'upload': 1} 

Tenga en cuenta, que en lugar de valores booleanos para "proceso" y las teclas "cargar" hay contadores.

Resulta, no podemos evitar la duplicación de estas palabras:

$ python short.py -V process process upload -- alfa.log beta.log 
{'--': True, 
'--pswd': None, 
'--user': None, 
'--verbose': True, 
'-h': False, 
'<logfile>': ['alfa.log', 'beta.log'], 
'process': 2, 
'upload': 1} 

Conclusiones

Diseño de buena interfaz de línea de comandos puede ser un reto en algún momento.

hay múltiples aspectos del programa basado en línea de comandos:

  • buen diseño de línea de comandos
  • Selección/Uso analizador adecuado

argparse ofrece mucho, pero restringe los posibles escenarios y puede volverse muy complejo.

Con docopt las cosas son mucho más cortas, a la vez que se conserva la legibilidad y ofrece un alto grado de flexibilidad. Si gestiona la obtención de argumentos analizados del diccionario y realiza algunas conversiones (en enteros, abriendo archivos ...) manualmente (o en otra biblioteca llamada schema), puede encontrar que docopt es adecuado para el análisis de línea de comandos.

+0

¡Nunca he oído hablar de docopt, gran sugerencia! –

+0

@TonvandenHeuvel Bueno. Solo quiero confirmar, todavía lo estoy usando como mi solución preferida para las interfaces de línea de comando. –

+0

Mejor respuesta evar, gracias por los ejemplos detallados. – jnovack

0

Uso append_const a una lista de acciones y luego comprobar que la lista se rellena:

parser.add_argument('-process', dest=actions, const="process", action='append_const') 
parser.add_argument('-upload', dest=actions, const="upload", action='append_const') 

args = parser.parse_args() 

if(args.actions == None): 
    parser.error('Error: No actions requested') 

Incluso se pueden especificar los métodos directamente dentro de las constantes.

def upload: 
    ... 

parser.add_argument('-upload', dest=actions, const=upload, action='append_const') 
args = parser.parse_args() 

if(args.actions == None): 
    parser.error('Error: No actions requested') 

else: 
    for action in args.actions: 
     action() 
2

Para http://bugs.python.org/issue11588 estoy explorando formas de generalizar el concepto mutually_exclusive_group para manejar casos como este.

Con este desarrollo argparse.py, https://github.com/hpaulj/argparse_issues/blob/nested/argparse.py Soy capaz de escribir:

parser = argparse.ArgumentParser(prog='PROG', 
    description='Log archiver arguments.') 
group = parser.add_usage_group(kind='any', required=True, 
    title='possible actions (at least one is required)') 
group.add_argument('-p', '--process', action='store_true') 
group.add_argument('-u', '--upload', action='store_true') 
args = parser.parse_args() 
print(args) 

que produce la siguiente help:

usage: PROG [-h] (-p | -u) 

Log archiver arguments. 

optional arguments: 
    -h, --help  show this help message and exit 

possible actions (at least one is required): 
    -p, --process 
    -u, --upload 

Este acepta entradas como '-u', '-hasta' , '--proc --up' etc.

Termina ejecutando una prueba similar a https://stackoverflow.com/a/6723066/901925, aunque t mensaje de error que tiene que ser más claro:

usage: PROG [-h] (-p | -u) 
PROG: error: some of the arguments process upload is required 

Me pregunto:

  • son los parámetros kind='any', required=True suficiente (aceptar cualquiera del grupo clara; al menos uno es requerido)?

  • es el uso (-p | -u) claro? Un grupo mutuamente excluyente requerido produce la misma cosa. ¿Hay alguna notación alternativa?

  • está utilizando un grupo como este más intuitivo que phihag's prueba simple?

7

Sé que esto es viejo como suciedad, pero la forma en que requieren una opción, pero prohíben más de un (XOR) es la siguiente:

parser = argparse.ArgumentParser() 
group = parser.add_mutually_exclusive_group(required=True) 
group.add_argument('-process', action='store_true') 
group.add_argument('-upload', action='store_true') 
args = parser.parse_args() 
print args 

Salida:

>opt.py 
usage: multiplot.py [-h] (-process | -upload) 
multiplot.py: error: one of the arguments -process -upload is required 

>opt.py -upload 
Namespace(process=False, upload=True) 

>opt.py -process 
Namespace(process=True, upload=False) 

>opt.py -upload -process 
usage: multiplot.py [-h] (-process | -upload) 
multiplot.py: error: argument -process: not allowed with argument -upload 
+0

Desafortunadamente, el OP no quiere un XOR. Es uno o ambos, pero no ninguno, por lo que su último caso de prueba no cumple con sus requisitos. – kdopen

+1

@kdopen: el encuestado sí aclaró que esta es una variación de la pregunta original, que encontré útil: "la forma de exigir una opción pero prohibir más de una" Tal vez la etiqueta de Stack Exchange requeriría una nueva pregunta en lugar. Pero tener presente esta respuesta aquí me ayudó ... –

+0

Voy a secundar la utilidad de esta respuesta, esto terminó siendo exactamente lo que estaba buscando. –

0

La mejor forma de hacerlo es mediante el uso del módulo incorporado de Python add_mutually_exclusive_group.

parser = argparse.ArgumentParser(description='Log archiver arguments.') 
group = parser.add_mutually_exclusive_group() 
group.add_argument('-process', action='store_true') 
group.add_argument('-upload', action='store_true') 
args = parser.parse_args() 

Si desea que sólo un argumento para ser seleccionado por línea de comandos solo uso requerido = True como un argumento para el grupo

group = parser.add_mutually_exclusive_group(required=True) 
Cuestiones relacionadas