130

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.

General Grievance
  • 4,555
  • 31
  • 31
  • 45
Aufwind
  • 25,310
  • 38
  • 109
  • 154
  • 10
    Why not use the logging module and set the log level INFO by default, and DEBUG when --verbose is passed? Best not to reimplement anything that's already available in the language... – Tim May 12 '11 at 15:08
  • 3
    @Tim, I agree, but the logging module is pretty painful. – mlissner Feb 08 '13 at 01:11
  • 1
    The `logging` module is not intended to communicate with the user. Each message level goes to `stderr` not `stdout`. – buhtz Jan 04 '23 at 13:08
  • 1
    @buhtz that's only the default of `StreamHandler`: you can pass `sys.stdout` and it will print to `stdout`. It may be overkill for simple cases, but [this](https://github.com/canepan/bot/blob/master/src/tools/libs/logging_utils.py#L30) is what I use for more complex cases – Nicola Apr 22 '23 at 03:18

9 Answers9

133

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.

kindall
  • 178,883
  • 35
  • 278
  • 309
  • 1
    Even better, do it as the `print` function: Accept many arguments. It can be implemented as `print(*args)` in 3.x and as `for arg in args: print arg,` in 2.x. The main advantage is that it allows mixing strings and things of other types in one message with without explicit `str` calls/formatting and concatenations. –  May 12 '11 at 16:19
  • What is the comma used for at the end of the `print arg,` line? – SamK Aug 07 '12 at 08:12
  • That's easily determined for one's self experimentally or by checking the documentation, but it suppresses the line break that would normally be printed. – kindall Aug 07 '12 at 16:10
  • 6
    The Python 3 print function also takes optional keyword argument, so to fully reproduce the functionality of print: `def verboseprint(*args, **kwargs): print(*args, **kwargs)` – lstyls Jun 04 '16 at 20:38
  • Why should one use this approach? There is absolutely no need for performance optimization here. Calling an empty function takes 55.6ns on my machine, a function which does nothing but "if False" takes 61.8ns. This is not relevant in practice. But on the other side, globally set parameters are always obstacles when it comes to unit testing. – lumbric Dec 29 '20 at 09:55
  • 1
    @lumbric This approach is not about performance optimization but about not writing dumb code. If you know the verbosity level at start of your program and you know it won't change it is pointless to constantly test it. – Jeyekomon Feb 03 '21 at 09:33
  • @Jeyekomon you could also argue that it's pointless to constantly call an empty function. It might be difficult to judge objectively, but personally I find things easier to read if the if is inside the function. – lumbric Feb 03 '21 at 15:03
78

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.

Profpatsch
  • 4,918
  • 5
  • 27
  • 32
  • 11
    As per the Python Docs [here](https://docs.python.org/2/howto/logging.html#logging-basic-tutorial), logging should not be used in cases where you only require to print output in the normal execution of the program. It looks like that is what the OP wants. – SANDeveloper Mar 03 '16 at 19:12
  • 2
    This seems fine for the basic problem but many *nix commands also support multiple levels of verbosity (-v -v -v, etc), which might get messy this way. – TextGeek Apr 09 '19 at 13:41
  • 1
    I think your first code example is missing `import argparse`, `p = argparse.ArgumentParser()` and `parser.add_argument('--verbose', '-v', action='count', default=0)` ? I guess the ellipse indicates that there's work to be done, but it's not obvious what's in store for those not in the know. Perhaps a link to a reference would do? – Heath Raftery Aug 22 '21 at 02:20
19

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.
mlissner
  • 17,359
  • 18
  • 106
  • 169
  • 1
    Why don’t you use the logging module for INFO and WARN? That is import it when `-v` is used. In your current solution everything is dumped to stdout instead of stderr. And: you normally want to relay every error to the user, don’t you? – Profpatsch Mar 14 '13 at 14:19
  • 2
    Yeah, that's a fair point. Logging has some cognitive overhead that I was trying to avoid, but it's probably the "right" thing to do. It's just annoyed me in the past... – mlissner Mar 14 '13 at 18:39
  • The `logging` module is [not the right answer](https://docs.python.org/2/howto/logging.html#logging-basic-tutorial). – buhtz Jan 04 '23 at 13:12
9

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.

jonesy
  • 3,502
  • 17
  • 23
5

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.

Michael
  • 8,362
  • 6
  • 61
  • 88
George V. Reilly
  • 15,885
  • 7
  • 43
  • 38
4

@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
Community
  • 1
  • 1
stefanct
  • 2,503
  • 1
  • 28
  • 32
4

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.

Lee-Man
  • 374
  • 1
  • 8
2

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.

Daniel Diniz
  • 175
  • 8
1

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.

user377367
  • 119
  • 1
  • 2