Como implementar a opção --verbose ou -v em um script?

93

Eu conheço o --verboseou -vde várias ferramentas e gostaria de implementar isso em alguns de meus próprios scripts e ferramentas.

Pensei em colocar:

if verbose:
    print ...

através do meu código-fonte, para que se um usuário passar a -vopção, a variável verboseserá definida como Truee o texto será impresso.

Esta é a abordagem certa ou existe uma forma mais comum?

Adição: não estou pedindo uma maneira de implementar a análise de argumentos. Que eu sei como isso é feito. Estou interessado apenas na opção prolixa.

Aufwind
fonte
9
Por que não usar o módulo de registro e definir o nível de registro INFO por padrão, e DEBUG quando --verbose for passado? Melhor não reimplementar nada que já esteja disponível no idioma ...
Tim
3
@Tim, eu concordo, mas o módulo de registro é muito doloroso.
mlissner

Respostas:

105

Minha sugestão é usar uma função. Mas em vez de colocar o ifna função, o que você pode ficar tentado a fazer, faça assim:

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

(Sim, você pode definir uma função em uma ifinstrução, e ela só será definida se a condição for verdadeira!)

Se você estiver usando Python 3, onde printjá existe uma função (ou se quiser usar printcomo uma função no uso de 2.x from __future__ import print_function), é ainda mais simples:

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

Dessa forma, a função é definida como não fazer nada se o modo detalhado estiver desativado (usando um lambda), em vez de testar constantemente o verbosesinalizador.

Se o usuário pudesse alterar o modo de verbosidade durante a execução do seu programa, esta seria a abordagem errada (você precisaria do ifna função), mas como você está configurando com um sinalizador de linha de comando, você só precisa tome a decisão uma vez.

Você então usa, por exemplo, verboseprint("look at all my verbosity!", object(), 3)sempre que quiser imprimir uma mensagem "detalhada".

kindall
fonte
1
Melhor ainda, faça-o como a printfunção: Aceite muitos argumentos. Pode ser implementado como print(*args)em 3.xe como for arg in args: print arg,em 2.x. A principal vantagem é que ele permite misturar strings e coisas de outros tipos em uma mensagem, sem strchamadas / formatação e concatenações explícitas .
Para que serve a vírgula no final da print arg,linha?
SamK
Isso é facilmente determinado por alguém experimentalmente ou verificando a documentação, mas suprime a quebra de linha que normalmente seria impressa.
kindall
5
A função de impressão do Python 3 também aceita um argumento de palavra-chave opcional, para reproduzir totalmente a funcionalidade de impressão:def verboseprint(*args, **kwargs): print(*args, **kwargs)
lstyls
61

Use o 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 estes vão automaticamente para 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 obter mais informações, consulte Python Docs e os tutoriais .

Profpatsch
fonte
8
De acordo com os documentos Python aqui , o registro não deve ser usado nos casos em que você só precisa imprimir a saída na execução normal do programa. Parece que é isso que o OP deseja.
SANDeveloper
1
Isso parece bom para o problema básico, mas muitos comandos * nix também suportam vários níveis de verbosidade (-v -v -v, etc), o que pode ficar confuso desta forma.
TextGeek de
12

Construindo e simplificando a resposta de @ kindall, aqui está o que eu normalmente uso:

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

Isso fornece o seguinte uso em todo o seu script:

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

E seu script pode ser chamado assim:

% 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

Algumas notas:

  1. Seu primeiro argumento é seu nível de erro e o segundo é sua mensagem. Ele tem o número mágico de 3que define o limite superior para seu registro, mas eu aceito isso como um compromisso pela simplicidade.
  2. Se você quiser v_printtrabalhar em todo o seu programa, terá que fazer o lixo com o global. Não é divertido, mas eu desafio alguém a encontrar uma maneira melhor.
mlissner
fonte
1
Por que você não usa o módulo de registro para INFO e WARN? Isso é importá-lo quando -vfor usado. Em sua solução atual, tudo é despejado em stdout ao invés de stderr. E: você normalmente deseja retransmitir todos os erros para o usuário, não é?
Profpatsch
2
Sim, é um ponto justo. Logging tem alguma sobrecarga cognitiva que eu estava tentando evitar, mas provavelmente é a coisa "certa" a fazer. Isso me incomodou no passado ...
mlissner
9

O que faço em meus scripts é verificar em tempo de execução se a opção 'verbose' está definida e, em seguida, definir meu nível de log para depuração. Se não estiver definido, eu o defino como info. Dessa forma, você não tem verificações 'if verbose' em todo o código.

Jonesy
fonte
2

Pode ser mais limpo se você tiver uma função, digamos chamada vprint, que verifica o sinalizador detalhado para você. Em seguida, basta chamar sua própria vprintfunção em qualquer lugar que desejar verbosidade opcional.

Lee-Man
fonte
2

Roubei o código de registro do virtualenv para um projeto meu. Olhe nos main()de virtualenv.pyver como ele é inicializado. O código é polvilhado com logger.notify(), logger.info(), logger.warn(), e similares. Que emita métodos realmente saída é determinado por se virtualenv foi invocada com -v, -vv, -vvv, ou -q.

George V. Reilly
fonte
2

A solução de @ kindall não funciona com meu Python versão 3.5. @styles afirma corretamente em seu comentário que o motivo é o argumento adicional de palavras-chave opcionais . Portanto, minha versão ligeiramente aprimorada para Python 3 se parece com isto:

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

Pode haver uma variável global, provavelmente definida com argparsefrom sys.argv, que indica se o programa deve ser detalhado ou não. Em seguida, um decorador poderia ser escrito de forma que, se a verbosidade estivesse ativada, a entrada padrão seria desviada para o dispositivo nulo, desde que a função fosse executada:

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

Essa resposta é inspirada neste código ; na verdade, eu ia apenas usá-lo como um módulo em meu programa, mas recebi erros que não consegui entender, então adaptei uma parte dele.

A desvantagem dessa solução é que a verbosidade é binária, ao contrário de logging, que permite um ajuste mais preciso de quão detalhado o programa pode ser. Além disso, todas as print chamadas são desviadas, o que pode ser indesejado.

Daniel Diniz
fonte
0

O que eu preciso é uma função que imprima um objeto (obj), mas apenas se a variável global verbose for true, caso contrário, ela não fará nada.

Desejo ser capaz de alterar o parâmetro global "verbose" a qualquer momento. Simplicidade e legibilidade para mim são de suma importância. Então, eu procederia como as seguintes linhas indicam:

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

A variável global "verbose" também pode ser definida na lista de parâmetros.

user377367
fonte