12

When using argparse, passing --help to the program generates help text. Unfortunately, it's hard to read because there are no blank lines between options. Here's an excerpt to illustrate:

optional arguments:
  -h, --help            show this help message and exit
  -u FILENAME, --up-sound FILENAME
                        The sound to play when the network comes up. Default:
                        "/path/to/some/sound/file.wav"
  -d FILENAME, --down-sound FILENAME
                        The sound to play when the network goes down. Default:
                        "/path/to/some/other/sound/file.wav"
  -p EXECUTABLE, --player EXECUTABLE
                        The program to use to play sounds. Default: "play"
  -s, --silent          If specified, network_monitor.py will not play any
                        sounds.
  -c, --no-clear-screen
                        If specified, screen will not be cleared (nor extra
                        blank lines added) before network_monitor.py runs.
  --version             show program's version number and exit

Notice that in some cases, such as between -p and -s or between -c and --version, it is difficult to tell at a glance which help text applies to which option. There should be a blank line between entries. For example:

  -p EXECUTABLE, --player EXECUTABLE
                        The program to use to play sounds. Default: "play"

  -s, --silent          If specified, network_monitor.py will not play any
                        sounds.

How can I accomplish this? Several other questions recommend using argparse.RawTextHelpFormatter. The problem with that is that if I use it, I have to write my own logic to wrap the help text as the raw text help formatter does no formatting. The obvious answer would be to append '\n\n' to the end of the help text and use the default formatter. But inexplicably, newlines get stripped.

What's the way forward here? I'm using Python 3.4.

Community
  • 1
  • 1
Scott Severance
  • 943
  • 10
  • 27

2 Answers2

11

You can create your own help text formatter that does this. Note that this requires you to be very specific to the implementation detail of the argparse.HelpFormatter. So consider this warning that is included in every help formatter type description:

Only the name of this class is considered a public API. All the methods provided by the class are considered an implementation detail.

Once we ignore that, creating our own help formatter that adds a blank line between the entries is very simple:

class BlankLinesHelpFormatter (argparse.HelpFormatter):
    def _split_lines(self, text, width):
        return super()._split_lines(text, width) + ['']

And that’s it. Now when you create the ArgumentParser object while passing formatter_class=BlankLinesHelpFormatter to the constructor, blank lines will appear between each argument in the help text.

poke
  • 369,085
  • 72
  • 557
  • 602
  • Just what I was looking for. Thanks. What do you suppose are the odds of this breaking in the future? – Scott Severance Apr 07 '15 at 23:17
  • 2
    The [source file](https://hg.python.org/cpython/log/default/Lib/argparse.py) hasn’t been touched that often in the past, and most of those changes had no impact on the interface. So despite the warning, I would say you are pretty fine for quite a while. Plus, by just calling the base method, my solution doesn’t depend on much and doesn’t reinvents any of the logic that may break in the future. – poke Apr 08 '15 at 06:16
  • `super(BlankLinesHelpFormatter, self)` for those of us who still haven't heard that Python 2.7 is end of life. – Oskar Skog Dec 04 '22 at 14:40
4

poke's approach is good. Note that RawTextHelpFormatter also modifies this method, simplifying it to:

def _split_lines(self, text, width):
    return text.splitlines()

poke's method could be tweaked to give you more control

class BlankLinesHelpFormatter (argparse.HelpFormatter):
    # add empty line if help ends with \n
    def _split_lines(self, text, width):
        lines = super()._split_lines(text, width)
        if text.endswith('\n'):
            lines += ['']
        return lines

With this:

parser = argparse.ArgumentParser(description='A description',
    formatter_class=BlankLinesHelpFormatter,
    epilog='Epilog line',
    )
parser.add_argument('-u', '--up-sound', metavar='FILENAME',
    help='The sound to play when the network comes up. Default:"%(default)s"\n',
    default="/path/to/some/sound/file.wav")
# note \n in above help
parser.add_argument('-d', '--down-sound', metavar='FILENAME',
    help='The sound to play when the network goes down. Default:"%(default)s"',
    default="/path/to/some/other/sound/file.wav")
parser.add_argument('-s','--silent', action='store_true',
    help='If specified, network_monitor.py will not play any sounds.')
parser.add_argument('positional', nargs='*', help='positional argument')
parser.print_help()

displays:

usage: stack29484443.py [-h] [-u FILENAME] [-d FILENAME] [-s]
                        [positional [positional ...]]

A description

positional arguments:
  positional            positional argument

optional arguments:
  -h, --help            show this help message and exit
  -u FILENAME, --up-sound FILENAME
                        The sound to play when the network comes up.
                        Default:"/path/to/some/sound/file.wav"

  -d FILENAME, --down-sound FILENAME
                        The sound to play when the network goes down.
                        Default:"/path/to/some/other/sound/file.wav"
  -s, --silent          If specified, network_monitor.py will not play any
                        sounds.

Epilog line

For reference, the default _split_lines is:

def _split_lines(self, text, width):
    text = self._whitespace_matcher.sub(' ', text).strip()
    return _textwrap.wrap(text, width)
    # self._whitespace_matcher = _re.compile(r'\s+')

This removes final \n and reduces all interior whitespace to one blank.

hpaulj
  • 221,503
  • 14
  • 230
  • 353