Comment implémenter l'option --verbose ou -v dans un script?

94

Je connais le --verboseou -vplusieurs outils et j'aimerais l'implémenter dans certains de mes propres scripts et outils.

J'ai pensé à placer:

if verbose:
    print ...

via mon code source, de sorte que si un utilisateur passe l' -voption, la variable verbosesera définie sur Trueet le texte sera imprimé.

Est-ce la bonne approche ou existe-t-il une méthode plus courante?

Ajout: Je ne demande pas un moyen d'implémenter l'analyse des arguments. Que je sais comment cela se fait. Je ne suis particulièrement intéressé que par l'option verbeuse.

Aufwind
la source
9
Pourquoi ne pas utiliser le module de journalisation et définir le niveau de journalisation INFO par défaut, et DEBUG lorsque --verbose est passé? Mieux vaut ne pas réimplémenter tout ce qui est déjà disponible dans la langue ...
Tim
3
@Tim, je suis d'accord, mais le module de journalisation est assez pénible.
mlissner

Réponses:

106

Ma suggestion est d'utiliser une fonction. Mais plutôt que de mettre le ifdans la fonction, ce que vous pourriez être tenté de faire, faites-le comme ceci:

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

(Oui, vous pouvez définir une fonction dans une ifinstruction, et elle ne sera définie que si la condition est vraie!)

Si vous utilisez Python 3, où printest déjà une fonction (ou si vous êtes prêt à l'utiliser printcomme fonction dans 2.x en utilisant from __future__ import print_function), c'est encore plus simple:

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

De cette façon, la fonction est définie comme une action à ne rien faire si le mode verbeux est désactivé (en utilisant un lambda), au lieu de tester constamment l' verboseindicateur.

Si l'utilisateur pouvait changer le mode de verbosité pendant l'exécution de votre programme, ce serait la mauvaise approche (vous auriez besoin du ifdans la fonction), mais puisque vous le définissez avec un indicateur de ligne de commande, il vous suffit de prendre la décision une fois.

Vous utilisez ensuite par exemple verboseprint("look at all my verbosity!", object(), 3)chaque fois que vous souhaitez imprimer un message "détaillé".

kindall
la source
1
Mieux encore, faites-le comme printfonction: Acceptez de nombreux arguments. Il peut être implémenté comme print(*args)dans 3.x et comme for arg in args: print arg,dans 2.x. Le principal avantage est qu'il permet de mélanger des chaînes et des objets d'autres types dans un message sans strappels / formatage et concaténations explicites .
À quoi sert la virgule à la fin de la print arg,ligne?
SamK
C'est facilement déterminé par soi-même expérimentalement ou en vérifiant la documentation, mais cela supprime le saut de ligne qui serait normalement imprimé.
kindall
5
La fonction d'impression de Python 3 prend également un argument de mot-clé facultatif, afin de reproduire pleinement la fonctionnalité d'impression:def verboseprint(*args, **kwargs): print(*args, **kwargs)
lstyls
61

Utilisez le loggingmodule:

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

Tous ces éléments vont automatiquement à 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.

Pour plus d'informations, consultez la documentation Python et les didacticiels .

Profpatsch
la source
8
Comme indiqué dans la documentation Python ici , la journalisation ne doit pas être utilisée dans les cas où vous n'avez besoin que d'imprimer la sortie dans l'exécution normale du programme. On dirait que c'est ce que veut le PO.
SANDeveloper
1
Cela semble bien pour le problème de base, mais de nombreuses commandes * nix prennent également en charge plusieurs niveaux de verbosité (-v -v -v, etc.), ce qui peut devenir compliqué de cette façon.
TextGeek
12

Construire et simplifier la réponse de @ kindall, voici ce que j'utilise généralement:

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

Cela fournit ensuite l'utilisation suivante tout au long de votre script:

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

Et votre script peut être appelé comme ceci:

% 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

Quelques remarques:

  1. Votre premier argument est votre niveau d'erreur et le second est votre message. Il a le nombre magique de3 qui définit la limite supérieure de votre journalisation, mais j'accepte cela comme un compromis pour la simplicité.
  2. Si vous voulez v_printtravailler tout au long de votre programme, vous devez faire les choses inutiles avec le global. Ce n'est pas amusant, mais je mets au défi quelqu'un de trouver un meilleur moyen.
mlissner
la source
1
Pourquoi n'utilisez-vous pas le module de journalisation pour INFO et WARN? C'est l'importer quand il -vest utilisé. Dans votre solution actuelle, tout est sauvegardé sur stdout au lieu de stderr. Et: vous voulez normalement relayer chaque erreur à l'utilisateur, n'est-ce pas?
Profpatsch
2
Ouais, c'est un bon point. La journalisation a des frais généraux cognitifs que j'essayais d'éviter, mais c'est probablement la «bonne» chose à faire. Ça m'a juste ennuyé dans le passé ...
mlissner
9

Ce que je fais dans mes scripts est de vérifier lors de l'exécution si l'option «verbose» est définie, puis de définir mon niveau de journalisation sur débogage. S'il n'est pas défini, je le règle sur info. De cette façon, vous n'avez pas de vérifications «if verbose» dans tout votre code.

jonesy
la source
2

Cela pourrait être plus propre si vous avez une fonction, disons appelée vprint, qui vérifie l'indicateur détaillé pour vous. Ensuite, vous appelez simplement votre propre vprintfonction à n'importe quel endroit où vous souhaitez une verbosité facultative.

Lee-Man
la source
2

J'ai volé le code de journalisation de virtualenv pour un de mes projets. Regardez dans main()de virtualenv.pyvoir comment il est initialisé. Le code est saupoudré avec logger.notify(), logger.info(), logger.warn()et autres. Quelles sont les méthodes effectivement sortie Emit est déterminée par le fait que virtualenv a été invoqué avec -v, -vv, -vvvou -q.

George V. Reilly
la source
2

La solution de @ kindall ne fonctionne pas avec ma version 3.5 de Python. @styles indique correctement dans son commentaire que la raison est l' argument de mots-clés facultatifs supplémentaires . D'où ma version légèrement raffinée pour Python 3 ressemble à ceci:

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

Il peut y avoir une variable globale, probablement définie avec argparsefrom sys.argv, qui indique si le programme doit être détaillé ou non. Ensuite, un décorateur pourrait être écrit de telle sorte que si la verbosité était activée, l'entrée standard serait détournée vers le périphérique nul tant que la fonction devait s'exécuter:

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

Cette réponse est inspirée de ce code ; en fait, j'allais simplement l'utiliser comme module dans mon programme, mais j'ai eu des erreurs que je ne pouvais pas comprendre, alors j'en ai adapté une partie.

L'inconvénient de cette solution est que la verbosité est binaire, contrairement à avec logging, qui permet d'ajuster plus finement le degré de verbosité du programme. De plus, tous les print appels sont détournés, ce qui peut être indésirable.

Daniel Diniz
la source
0

Ce dont j'ai besoin, c'est d'une fonction qui imprime un objet (obj), mais seulement si la variable globale verbose est vraie, sinon elle ne fait rien.

Je souhaite pouvoir modifier à tout moment le paramètre global "verbose". La simplicité et la lisibilité sont pour moi d'une importance capitale. Je procéderais donc comme l'indiquent les lignes suivantes:

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 globale "verbose" peut également être définie à partir de la liste des paramètres.

user377367
la source