¿Cómo implementar la opción --verbose o -v en un script?

94

Conozco --verboseo -vde varias herramientas y me gustaría implementar esto en algunos de mis propios scripts y herramientas.

Pensé en colocar:

if verbose:
    print ...

a través de mi código fuente, de modo que si un usuario pasa la -vopción, la variable verbosese establecerá en Truey se imprimirá el texto.

¿Es este el enfoque correcto o hay una forma más común?

Además: no estoy pidiendo una forma de implementar el análisis de argumentos. Que yo se como se hace. Solo me interesa especialmente la opción detallada.

Aufwind
fuente
9
¿Por qué no utilizar el módulo de registro y establecer el nivel de registro INFO de forma predeterminada y DEBUG cuando se pasa --verbose? Es mejor no volver a implementar nada que ya esté disponible en el idioma ...
Tim
3
@Tim, estoy de acuerdo, pero el módulo de registro es bastante doloroso.
mlissner

Respuestas:

106

Mi sugerencia es utilizar una función. Pero en lugar de poner el ifen la función, lo que podría tener la tentación de hacer, hágalo así:

if verbose:
    def verboseprint(*args):
        # Print each argument separately so caller doesn't need to
        # stuff everything to be printed into a single string
        for arg in args:
           print arg,
        print
else:   
    verboseprint = lambda *a: None      # do-nothing function

(Sí, puede definir una función en un if declaración, ¡y solo se definirá si la condición es verdadera!)

Si está usando Python 3, donde printya hay una función (o si está dispuesto a usar printcomo función en 2.x usando from __future__ import print_function) es aún más simple:

verboseprint = print if verbose else lambda *a, **k: None

De esta manera, la función se define como no hacer nada si el modo detallado está desactivado (usando una lambda), en lugar de probar constantemente la verbosebandera.

Si el usuario pudiera cambiar el modo de verbosidad durante la ejecución de su programa, este sería el enfoque incorrecto (necesitaría el ifen la función), pero como lo está configurando con una marca de línea de comandos, solo necesita tomar la decisión una vez.

A continuación, utilice, por ejemplo, verboseprint("look at all my verbosity!", object(), 3)cada vez que desee imprimir un mensaje "detallado".

un poco
fuente
1
Mejor aún, hazlo como la printfunción: Acepta muchos argumentos. Se puede implementar como print(*args)en 3.xy como for arg in args: print arg,en 2.x. La principal ventaja es que permite mezclar cadenas y cosas de otros tipos en un mensaje sin strllamadas / formateo explícitos ni concatenaciones.
¿Para qué se usa la coma al final de la print arg,línea?
SamK
Eso se determina fácilmente por uno mismo de forma experimental o comprobando la documentación, pero suprime el salto de línea que normalmente se imprimiría.
todo el
5
La función de impresión de Python 3 también toma un argumento de palabra clave opcional, por lo que para reproducir completamente la funcionalidad de impresión:def verboseprint(*args, **kwargs): print(*args, **kwargs)
lstyls
61

Utilice el loggingmódulo:

import logging as log

args = p.parse_args()
if args.verbose:
    log.basicConfig(format="%(levelname)s: %(message)s", level=log.DEBUG)
    log.info("Verbose output.")
else:
    log.basicConfig(format="%(levelname)s: %(message)s")

log.info("This should be verbose.")
log.warning("This is a warning.")
log.error("This is an error.")

Todos estos van automáticamente a stderr:

% python myprogram.py
WARNING: This is a warning.
ERROR: This is an error.

% python myprogram.py -v
INFO: Verbose output.
INFO: This should be verbose.
WARNING: This is a warning.
ERROR: This is an error.

Para obtener más información, consulte Python Docs y los tutoriales .

Profpatsch
fuente
8
Según los documentos de Python aquí , el registro no debe usarse en los casos en que solo requiera imprimir la salida en la ejecución normal del programa. Parece que eso es lo que quiere el OP.
SANDeveloper
1
Esto parece estar bien para el problema básico, pero muchos comandos * nix también admiten múltiples niveles de verbosidad (-v -v -v, etc.), lo que podría complicarse de esta manera.
TextGeek
12

Construyendo y simplificando la respuesta de @ kindall, esto es lo que uso normalmente:

v_print = None
def main()
    parser = argparse.ArgumentParser()
    parser.add_argument('-v', '--verbosity', action="count", 
                        help="increase output verbosity (e.g., -vv is more than -v)")

    args = parser.parse_args()

    if args.verbosity:
        def _v_print(*verb_args):
            if verb_args[0] > (3 - args.verbosity):
                print verb_args[1]  
    else:
        _v_print = lambda *a: None  # do-nothing function

    global v_print
    v_print = _v_print

if __name__ == '__main__':
    main()

Esto luego proporciona el siguiente uso en todo su script:

v_print(1, "INFO message")
v_print(2, "WARN message")
v_print(3, "ERROR message")

Y su script se puede llamar así:

% python verbose-tester.py -v
ERROR message

% python verbose=tester.py -vv
WARN message
ERROR message

% python verbose-tester.py -vvv
INFO message
WARN message
ERROR message

Un par de notas:

  1. Su primer argumento es su nivel de error y el segundo es su mensaje. Tiene el numero magico de3 que establece el límite superior para su registro, pero lo acepto como un compromiso por simplicidad.
  2. Si quieres v_printtrabajar a lo largo de tu programa, tienes que hacer la basura con lo global. No es divertido, pero desafío a alguien a encontrar una manera mejor.
mlissner
fuente
1
¿Por qué no usa el módulo de registro para INFO y WARN? Eso es importarlo cuando -vse usa. En su solución actual, todo se vuelca a stdout en lugar de stderr. Y: normalmente desea transmitir todos los errores al usuario, ¿no es así?
Profpatsch
2
Sí, ese es un buen punto. El registro tiene una sobrecarga cognitiva que estaba tratando de evitar, pero probablemente sea lo "correcto". Me ha molestado en el pasado ...
mlissner
9

Lo que hago en mis scripts es verificar en tiempo de ejecución si la opción 'verbosa' está configurada y luego configurar mi nivel de registro para depurar. Si no está configurado, lo configuro en info. De esta manera, no tendrá comprobaciones "si es detallado" en todo el código.

Jonesy
fuente
2

Podría ser más limpio si tiene una función, digamos llamada vprint, que verifica la bandera detallada por usted. Luego, simplemente llame a su propia vprintfunción en cualquier lugar que desee verbosidad opcional.

Lee-Man
fuente
2

Robé el código de registro de virtualenv para un proyecto mío. Mira en el main()de virtualenv.pyver cómo se ha inicializado. El código se roció con logger.notify(), logger.info(), logger.warn(), y similares. Qué métodos en realidad salida Emit está determinado por si virtualenv se invocó con -v, -vv, -vvv, o -q.

George V. Reilly
fuente
2

La solución de @ kindall no funciona con mi versión 3.5 de Python. @styles indica correctamente en su comentario que la razón es el argumento de palabras clave opcionales adicionales . Por lo tanto, mi versión ligeramente refinada para Python 3 se ve así:

if VERBOSE:
    def verboseprint(*args, **kwargs):
        print(*args, **kwargs)
else:
    verboseprint = lambda *a, **k: None # do-nothing function
Stefanct
fuente
1

Podría haber una variable global, probablemente establecida con argparsefrom sys.argv, que represente si el programa debe ser detallado o no. Luego, se podría escribir un decorador de manera que si la verbosidad estuviera activada, la entrada estándar se desviaría al dispositivo nulo siempre que la función se ejecutara:

import os
from contextlib import redirect_stdout
verbose = False

def louder(f):
    def loud_f(*args, **kwargs):
        if not verbose:
            with open(os.devnull, 'w') as void:
                with redirect_stdout(void):
                    return f(*args, **kwargs)
        return f(*args, **kwargs)
    return loud_f

@louder
def foo(s):
    print(s*3)

foo("bar")

Esta respuesta está inspirada en este código ; en realidad, lo iba a usar como un módulo en mi programa, pero obtuve errores que no podía entender, así que adapté una parte.

La desventaja de esta solución es que la verbosidad es binaria, a diferencia de con logging, lo que permite un ajuste más fino de cuán detallado puede ser el programa. Además, todas las print llamadas se desvían, lo que podría ser no deseado.

Daniel Diniz
fuente
0

Lo que necesito es una función que imprima un objeto (obj), pero solo si la variable global detallada es verdadera, de lo contrario no hace nada.

Quiero poder cambiar el parámetro global "verbose" en cualquier momento. Para mí, la sencillez y la legibilidad son de suma importancia. Entonces procedería como lo indican las siguientes líneas:

ak@HP2000:~$ python3
Python 3.4.3 (default, Oct 14 2015, 20:28:29) 
[GCC 4.8.4] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> verbose = True
>>> def vprint(obj):
...     if verbose:
...         print(obj)
...     return
... 
>>> vprint('Norm and I')
Norm and I
>>> verbose = False
>>> vprint('I and Norm')
>>> 

La variable global "detallada" también se puede configurar desde la lista de parámetros.

usuario377367
fuente