Aufwind
Aufwind

Reputation: 26278

How to implement the --verbose or -v option into a script?

I know the --verbose or -v from several tools and I'd like to implement this into some of my own scripts and tools.

I thought of placing:

if verbose:
    print ...

through my source code, so that if a user passes the -v option, the variable verbose will be set to True and the text will be printed.

Is this the right approach or is there a more common way?

Addition: I am not asking for a way to implement the parsing of arguments. That I know how it is done. I am only interested specially in the verbose option.

Upvotes: 136

Views: 208765

Answers (9)

kindall
kindall

Reputation: 184345

My suggestion is to use a function. But rather than putting the if in the function, which you might be tempted to do, do it like this:

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

(Yes, you can define a function in an if statement, and it'll only get defined if the condition is true!)

If you're using Python 3, where print is already a function (or if you're willing to use print as a function in 2.x using from __future__ import print_function) it's even simpler:

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

This way, the function is defined as a do-nothing if verbose mode is off (using a lambda), instead of constantly testing the verbose flag.

If the user could change the verbosity mode during the run of your program, this would be the wrong approach (you'd need the if in the function), but since you're setting it with a command-line flag, you only need to make the decision once.

You then use e.g. verboseprint("look at all my verbosity!", object(), 3) whenever you want to print a "verbose" message.

If you are willing and able to use the Python -O flag to turn verbosity on and off when launching the "production" version of the script (or set the PYTHONOPTIMIZE environment variable) then the better way is to test the __debug__ flag everywhere you want to print a verbose output:

if __debug__: print("Verbosity enabled")

When run without optimization, these statements are executed (and the if is stripped out, leaving only the body of the statement). When run with optimization, Python actually strips those statements out entirely. They have no performance impact whatsoever! See my blog post on __debug__ and -O for a more in-depth discussion.

Upvotes: 142

Daniel Diniz
Daniel Diniz

Reputation: 174

There could be a global variable, likely set with argparse from sys.argv, that stands for whether the program should be verbose or not. Then a decorator could be written such that if verbosity was on, then the standard input would be diverted into the null device as long as the function were to run:

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

This answer is inspired by this code; actually, I was going to just use it as a module in my program, but I got errors I couldn't understand, so I adapted a portion of it.

The downside of this solution is that verbosity is binary, unlike with logging, which allows for finer-tuning of how verbose the program can be. Also, all print calls are diverted, which might be unwanted for.

Upvotes: 2

stefanct
stefanct

Reputation: 2964

@kindall's solution does not work with my Python version 3.5. @styles correctly states in his comment that the reason is the additional optional keywords argument. Hence my slightly refined version for Python 3 looks like this:

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

Upvotes: 5

George V. Reilly
George V. Reilly

Reputation: 16333

I stole the logging code from virtualenv for a project of mine. Look in main() of virtualenv.py to see how it's initialized. The code is sprinkled with logger.notify(), logger.info(), logger.warn(), and the like. Which methods actually emit output is determined by whether virtualenv was invoked with -v, -vv, -vvv, or -q.

Upvotes: 5

user377367
user377367

Reputation: 119

What I need is a function which prints an object (obj), but only if global variable verbose is true, else it does nothing.

I want to be able to change the global parameter "verbose" at any time. Simplicity and readability to me are of paramount importance. So I would proceed as the following lines indicate:

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

Global variable "verbose" can be set from the parameter list, too.

Upvotes: 1

Profpatsch
Profpatsch

Reputation: 5058

Use the logging module:

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

All of these automatically go to 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.

For more info, see the Python Docs and the tutorials.

Upvotes: 81

mlissner
mlissner

Reputation: 18206

Building and simplifying @kindall's answer, here's what I typically use:

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

This then provides the following usage throughout your script:

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

And your script can be called like this:

% 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

A couple notes:

  1. Your first argument is your error level, and the second is your message. It has the magic number of 3 that sets the upper bound for your logging, but I accept that as a compromise for simplicity.
  2. If you want v_print to work throughout your program, you have to do the junk with the global. It's no fun, but I challenge somebody to find a better way.

Upvotes: 20

jonesy
jonesy

Reputation: 3542

What I do in my scripts is check at runtime if the 'verbose' option is set, and then set my logging level to debug. If it's not set, I set it to info. This way you don't have 'if verbose' checks all over your code.

Upvotes: 9

Lee-Man
Lee-Man

Reputation: 414

It might be cleaner if you have a function, say called vprint, that checks the verbose flag for you. Then you just call your own vprint function any place you want optional verbosity.

Upvotes: 6

Related Questions