Wie implementiere ich die Option --verbose oder -v in ein Skript?

93

Ich kenne das --verboseoder -vvon mehreren Tools und möchte dies in einige meiner eigenen Skripte und Tools implementieren.

Ich dachte an die Platzierung:

if verbose:
    print ...

Wenn ein Benutzer die -vOption übergibt , wird die Variable verboseauf gesetzt Trueund der Text gedruckt.

Ist dies der richtige Ansatz oder gibt es einen allgemeineren Weg?

Ergänzung: Ich frage nicht nach einer Möglichkeit, das Parsen von Argumenten zu implementieren. Dass ich weiß, wie es gemacht wird. Ich interessiere mich nur speziell für die ausführliche Option.

Aufwind
quelle
9
Warum nicht das Protokollierungsmodul verwenden und standardmäßig die Protokollstufe INFO und DEBUG festlegen, wenn --verbose übergeben wird? Am besten nichts neu implementieren, was bereits in der Sprache verfügbar ist ...
Tim
3
@ Tim, ich stimme zu, aber das Protokollierungsmodul ist ziemlich schmerzhaft.
mlissner

Antworten:

104

Mein Vorschlag ist, eine Funktion zu verwenden. Aber anstatt das ifin die Funktion zu setzen, zu der Sie möglicherweise versucht sind, gehen Sie folgendermaßen vor:

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

(Ja, Sie können eine Funktion in einer ifAnweisung definieren, die nur definiert wird, wenn die Bedingung erfüllt ist!)

Wenn Sie Python 3 verwenden, wo printbereits eine Funktion vorhanden ist (oder wenn Sie bereit sind, sie printin 2.x als Funktion zu verwenden from __future__ import print_function), ist dies noch einfacher:

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

Auf diese Weise wird die Funktion als Nichtstun definiert, wenn der ausführliche Modus deaktiviert ist (mit einem Lambda), anstatt das verboseFlag ständig zu testen .

Wenn der Benutzer den Ausführlichkeitsmodus während der Ausführung Ihres Programms ändern könnte , wäre dies der falsche Ansatz (Sie benötigen den ifin der Funktion), aber da Sie ihn mit einem Befehlszeilenflag setzen, müssen Sie nur Treffen Sie die Entscheidung einmal.

Sie verwenden dann zB verboseprint("look at all my verbosity!", object(), 3)immer dann, wenn Sie eine "ausführliche" Nachricht drucken möchten.

irgendwie
quelle
1
Noch besser, machen Sie es als printFunktion: Akzeptieren Sie viele Argumente. Es kann wie print(*args)in 3.x und wie for arg in args: print arg,in 2.x implementiert werden . Der Hauptvorteil besteht darin, dass Zeichenfolgen und andere Typen in einer Nachricht ohne explizite strAufrufe / Formatierungen und Verkettungen gemischt werden können.
Wofür wird das Komma am Ende der print arg,Zeile verwendet?
SamK
Das lässt sich leicht experimentell oder durch Überprüfen der Dokumentation feststellen, unterdrückt jedoch den Zeilenumbruch, der normalerweise gedruckt wird.
Kindall
4
Die Python 3-Druckfunktion verwendet auch ein optionales Schlüsselwortargument, um die Funktionalität des Drucks vollständig zu reproduzieren:def verboseprint(*args, **kwargs): print(*args, **kwargs)
lstyls
61

Verwenden Sie das loggingModul:

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

Alle diese gehen automatisch zu 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.

Weitere Informationen finden Sie in den Python-Dokumenten und in den Tutorials .

Profpatsch
quelle
8
Gemäß den Python-Dokumenten hier sollte die Protokollierung nicht in Fällen verwendet werden, in denen Sie die Ausgabe nur in der normalen Ausführung des Programms drucken müssen. Es sieht so aus, als ob das OP das will.
Sandentwickler
1
Dies scheint für das Grundproblem in Ordnung zu sein, aber viele * nix-Befehle unterstützen auch mehrere Ausführlichkeitsstufen (-v -v -v usw.), die auf diese Weise möglicherweise unübersichtlich werden.
TextGeek
12

Um die Antwort von @ kindall zu erstellen und zu vereinfachen, verwende ich normalerweise Folgendes:

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

Dies bietet dann die folgende Verwendung in Ihrem Skript:

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

Und Ihr Skript kann folgendermaßen aufgerufen werden:

% 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

Ein paar Anmerkungen:

  1. Ihr erstes Argument ist Ihre Fehlerstufe und das zweite Ihre Nachricht. Es hat die magische Zahl 3, die die Obergrenze für Ihre Protokollierung festlegt, aber ich akzeptiere dies der Einfachheit halber als Kompromiss.
  2. Wenn Sie v_printwährend Ihres gesamten Programms arbeiten möchten , müssen Sie den Müll mit dem globalen erledigen. Es macht keinen Spaß, aber ich fordere jemanden auf, einen besseren Weg zu finden.
mlissner
quelle
1
Warum verwenden Sie das Protokollierungsmodul nicht für INFO und WARN? Das heißt importieren, wenn -ves verwendet wird. In Ihrer aktuellen Lösung wird alles auf stdout anstatt auf stderr ausgegeben. Und: Normalerweise möchten Sie jeden Fehler an den Benutzer weiterleiten, nicht wahr?
Profpatsch
2
Ja, das ist ein fairer Punkt. Die Protokollierung hat einen gewissen kognitiven Aufwand, den ich vermeiden wollte, aber wahrscheinlich ist es das "Richtige". Es hat mich in der Vergangenheit nur geärgert ...
mlissner
9

In meinen Skripten überprüfe ich zur Laufzeit, ob die Option "Ausführlich" aktiviert ist, und setze dann meine Protokollierungsstufe auf "Debuggen". Wenn es nicht eingestellt ist, setze ich es auf info. Auf diese Weise müssen Sie Ihren gesamten Code nicht "wenn ausführlich" überprüfen.

jonesy
quelle
2

Es könnte sauberer sein, wenn Sie eine Funktion haben, beispielsweise aufgerufen vprint, die das ausführliche Flag für Sie überprüft. Dann rufen Sie einfach Ihre eigene vprintFunktion an einer beliebigen Stelle auf, an der Sie eine optionale Ausführlichkeit wünschen.

Lee-Man
quelle
2

Ich habe den Protokollierungscode von virtualenv für ein Projekt von mir gestohlen . Schauen Sie in nach, main()um virtualenv.pyzu sehen, wie es initialisiert wird. Der Code ist bestreut mit logger.notify(), logger.info(), logger.warn()und dergleichen. Welche Methoden tatsächlich Emit Ausgang dadurch bestimmt wird , ob mit virtualenv aufgerufen wurde -v, -vv, -vvv, oder -q.

George V. Reilly
quelle
1

Die Lösung von @ kindall funktioniert nicht mit meiner Python-Version 3.5. @styles gibt in seinem Kommentar korrekt an, dass der Grund das zusätzliche optionale Schlüsselwortargument ist . Daher sieht meine leicht verfeinerte Version für Python 3 folgendermaßen aus:

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

Es könnte eine globale Variable geben, die wahrscheinlich mit argparsefrom gesetzt ist sys.argvund dafür steht, ob das Programm ausführlich sein soll oder nicht. Dann könnte ein Dekorateur so geschrieben werden, dass bei aktivierter Ausführlichkeit die Standardeingabe in das Nullgerät umgeleitet wird, solange die Funktion ausgeführt wird:

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

Diese Antwort ist von diesem Code inspiriert . Eigentlich wollte ich es nur als Modul in meinem Programm verwenden, aber ich bekam Fehler, die ich nicht verstehen konnte, also habe ich einen Teil davon angepasst.

Der Nachteil dieser Lösung ist, dass die Ausführlichkeit im Gegensatz zu binär ist logging, was eine genauere Abstimmung der Ausführlichkeit des Programms ermöglicht. Außerdem werden alle print Anrufe umgeleitet, was möglicherweise unerwünscht ist.

Daniel Diniz
quelle
0

Was ich brauche, ist eine Funktion, die ein Objekt (obj) druckt, aber nur wenn die globale Variable verbose wahr ist, sonst tut sie nichts.

Ich möchte den globalen Parameter "verbose" jederzeit ändern können. Einfachheit und Lesbarkeit sind für mich von größter Bedeutung. Ich würde also wie folgt vorgehen:

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

Die globale Variable "verbose" kann auch aus der Parameterliste eingestellt werden.

user377367
quelle