Jak zaimplementować opcję --verbose lub -v w skrypcie?

95

Wiem, że --verbosealbo -vz kilku narzędzi i chciałbym zaimplementować to w niektórych moich własnych skryptów i narzędzi.

Myślałem o umieszczeniu:

if verbose:
    print ...

poprzez mój kod źródłowy, więc jeśli użytkownik przekaże -vopcję, zmienna verbosezostanie ustawiona na, Truea tekst zostanie wydrukowany.

Czy to właściwe podejście, czy też jest bardziej powszechny sposób?

Dodatek: nie proszę o sposób implementacji parsowania argumentów. Że wiem, jak to się robi. Szczególnie interesuje mnie tylko opcja gadatliwa.

Aufwind
źródło
9
Dlaczego nie skorzystać z modułu rejestrowania i domyślnie ustawić INFO poziomu dziennika oraz DEBUG, gdy podano --verbose? Najlepiej nie wdrażać ponownie niczego, co jest już dostępne w języku ...
Tim
3
@Tim, zgadzam się, ale moduł logowania jest dość bolesny.
mlissner

Odpowiedzi:

109

Moja sugestia to użycie funkcji. Ale zamiast umieszczać funkcję ifw funkcji, o którą możesz się kusić, zrób to w następujący sposób:

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

(Tak, możesz zdefiniować funkcję w ifinstrukcji, a zostanie ona zdefiniowana tylko wtedy, gdy warunek jest prawdziwy!)

Jeśli używasz Pythona 3, gdzie printjest już funkcją (lub jeśli chcesz użyć jej printjako funkcji w wersji 2.x przy użyciu from __future__ import print_function), jest to jeszcze prostsze:

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

W ten sposób funkcja jest definiowana jako nic nie rób, jeśli tryb gadatliwy jest wyłączony (przy użyciu lambda), zamiast ciągłego testowania verboseflagi.

Gdyby użytkownik mógł zmienić tryb oznajmiania podczas wykonywania programu, byłoby to niewłaściwe podejście (potrzebowałbyś iffunkcji), ale ponieważ ustawiasz go flagą wiersza poleceń, musisz tylko podejmij decyzję raz.

Możesz wtedy użyć np. Zawsze, verboseprint("look at all my verbosity!", object(), 3)gdy chcesz wydrukować "gadatliwą" wiadomość.

kindall
źródło
1
Jeszcze lepiej, zrób to jako printfunkcję: Akceptuj wiele argumentów. Może być zaimplementowany tak jak print(*args)w 3.x i for arg in args: print arg,2.x. Główną zaletą jest to, że umożliwia mieszanie łańcuchów i rzeczy innych typów w jednej wiadomości bez jawnych strwywołań / formatowania i konkatenacji.
Do czego służy przecinek na końcu print arg,wiersza?
SamK
Można to łatwo określić samodzielnie eksperymentalnie lub sprawdzając dokumentację, ale eliminuje to podział wiersza, który normalnie zostałby wydrukowany.
kindall
5
Funkcja print w Pythonie 3 przyjmuje również opcjonalny argument słowa kluczowego, aby w pełni odtworzyć funkcjonalność print:def verboseprint(*args, **kwargs): print(*args, **kwargs)
lstyls
63

Skorzystaj z loggingmodułu:

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

Wszystkie z nich automatycznie trafiają do 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.

Aby uzyskać więcej informacji, zobacz dokumentację języka Python i samouczki .

Profpatsch
źródło
8
Zgodnie z dokumentami Pythona tutaj , rejestrowanie nie powinno być używane w przypadkach, gdy potrzebujesz tylko wydrukować dane wyjściowe podczas normalnego wykonywania programu. Wygląda na to, że tego chce OP.
SANDeveloper
1
Wydaje się to w porządku w przypadku podstawowego problemu, ale wiele poleceń * nix obsługuje również wiele poziomów gadatliwości (-v -v -v, itp.), Które mogą być w ten sposób nieuporządkowane.
TextGeek
12

Budując i upraszczając odpowiedź @ kindall, oto czego zwykle używam:

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

Zapewnia to następujące użycie w całym skrypcie:

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

A twój skrypt można nazwać w ten sposób:

% 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

Kilka uwag:

  1. Twój pierwszy argument to poziom błędu, a drugi to Twoja wiadomość. Ma magiczną liczbę, 3która wyznacza górną granicę dla twojego logowania, ale przyjmuję to jako kompromis dla prostoty.
  2. Jeśli chcesz v_printpracować nad całym programem, musisz pozbyć się śmieci z globalnymi. To nie jest zabawne, ale rzucam wyzwanie komuś, aby znalazł lepszy sposób.
mlissner
źródło
1
Dlaczego nie używasz modułu logowania dla INFO i WARN? To znaczy zaimportuj go, gdy -vjest używany. W obecnym rozwiązaniu wszystko jest zrzucane na stdout zamiast na stderr. I: normalnie chcesz przekazać użytkownikowi każdy błąd, prawda?
Profpatsch
2
Tak, to słuszna uwaga. Rejestrowanie wiąże się z pewnym obciążeniem poznawczym, którego starałem się uniknąć, ale prawdopodobnie jest to „właściwa” rzecz. W przeszłości mnie to tylko denerwowało ...
mlissner
9

To, co robię w moich skryptach, to sprawdzanie w czasie wykonywania, czy opcja „verbose” jest ustawiona, a następnie ustawianie poziomu rejestrowania na debugowanie. Jeśli nie jest ustawiony, ustawiam go na info. W ten sposób nie masz sprawdzania „if verbose” w całym kodzie.

Jonesy
źródło
2

Może to być czystsze, jeśli masz funkcję, powiedzmy wywołaną vprint, która sprawdza za Ciebie flagę gadatliwości. Następnie po prostu wywołujesz swoją vprintfunkcję w dowolnym miejscu, w którym chcesz opcjonalną szczegółowość.

Lee-Man
źródło
2

Ukradłem kod logowania z virtualenv do mojego projektu. Zajrzyj main()do, virtualenv.pyaby zobaczyć, jak jest zainicjowany. Kod jest posypane logger.notify(), logger.info(), logger.warn(), i tym podobne. Jakie metody faktycznie wyjście emitować określenia czy virtualenv została wywołana z -v, -vv, -vvv, lub -q.

George V. Reilly
źródło
2

Rozwiązanie @ kindall nie działa z moim Pythonem w wersji 3.5. @styles poprawnie stwierdza w swoim komentarzu, że powodem jest dodatkowy opcjonalny argument słów kluczowych . Stąd moja nieco dopracowana wersja dla Pythona 3 wygląda tak:

if VERBOSE:
    def verboseprint(*args, **kwargs):
        print(*args, **kwargs)
else:
    verboseprint = lambda *a, **k: None # do-nothing function
stefanct
źródło
1

Mogłaby istnieć zmienna globalna, prawdopodobnie ustawiona za pomocą argparsefrom sys.argv, która określa , czy program powinien być gadatliwy, czy nie. Następnie można by napisać dekorator w taki sposób, że gdyby była włączona gadatliwość, standardowe wejście byłoby przekierowane do urządzenia zerowego, o ile funkcja miałaby działać:

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

Ta odpowiedź jest inspirowana tym kodem ; właściwie zamierzałem użyć go po prostu jako modułu w moim programie, ale dostałem błędy, których nie mogłem zrozumieć, więc dostosowałem część z nich.

Wadą tego rozwiązania jest to, że gadatliwość jest binarna, w przeciwieństwie do with logging, co pozwala na dokładniejsze dostrojenie tego, jak rozwlekły może być program. Ponadto wszystkie print połączenia są przekierowywane, co może być niepożądane.

Daniel Diniz
źródło
0

Potrzebuję funkcji, która drukuje obiekt (obj), ale tylko wtedy, gdy zmienna globalna verbose jest prawdziwa, w przeciwnym razie nic nie robi.

Chcę mieć możliwość zmiany globalnego parametru „verbose” w dowolnym momencie. Prostota i czytelność są dla mnie najważniejsze. Więc postąpiłbym tak, jak wskazują następujące linie:

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

Z listy parametrów można również ustawić zmienną globalną „verbose”.

user377367
źródło