38

I'm using argparse to take in command line input and also to produce help text. I want to use ArgumentDefaultsHelpFormatter as the formatter_class, however this prevents me from also using RawDescriptionHelpFormatter which would allow me to add custom formatting to my description or epilog.

Is there a sensible method of achieving this aside from writing code to produce text for default values myself? According to the argparse docs, all internals of ArgumentParser are considered implementation details, not public API, so sub-classing isn't an attractive option.

Spycho
  • 7,698
  • 3
  • 34
  • 55

2 Answers2

53

I just tried a multiple inheritance approach, and it works:

class CustomFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter):
    pass

parser = argparse.ArgumentParser(description='test\ntest\ntest.',
                                 epilog='test\ntest\ntest.',
                                 formatter_class=CustomFormatter)

This may break if the internals of these classes change though.

Spycho
  • 7,698
  • 3
  • 34
  • 55
  • Subclassing in this way does not depend on the internals. If the multiple inheritance works now, it should in all future changes. As my solution shows, the two classes are altering different formatter methods. – hpaulj Aug 27 '13 at 17:28
  • Another example using this multiple inheritance: http://stackoverflow.com/questions/23567393/pythons-argh-library-preserve-docstring-formatting-in-help-message/23583350#23583350 – hpaulj May 12 '14 at 18:38
  • @hpaulj The formatter methods aren't part of the public API. From the docstring of `argparse.HelpFormatter`: `Only the name of this class is considered a public API. All the methods provided by the class are considered an implementation detail.`. Both classes could be entirely rewritten. – Tim Diels Dec 10 '15 at 23:34
  • 4
    On second thought, argparse's author mentioned this is the recommended solution in [this issue (2011)](http://bugs.python.org/issue13023). I suppose that makes this approach unofficially supported for versions to come. – Tim Diels Dec 10 '15 at 23:53
  • 1
    All that means is that in the distant future `argparse` developers might rewrite the `HelpFormatter` class, and break your customization. However my experience with the process is that changes come slowly and with due consideration (maybe excessive) for backwards compatibility. – hpaulj Dec 11 '15 at 00:05
  • This is pure genius! Thanks! – Labo Jan 04 '18 at 21:14
3

I don't see why subclassing a HelpFormatter should be a problem. That isn't messing with the internals of ArgumentParser. The documentation has examples of custom Action and Type classes (or functions). I take the 'there are four such classes' line to be an invitation to write my own HelpFormatter if needed.

The provided HelpFormatter subclasses make quite simple changes, changing just one function. So they can be easily copied or altered.

RawDescription just changes:

def _fill_text(self, text, width, indent):
    return ''.join(indent + line for line in text.splitlines(keepends=True))

In theory it could be changed without altering the API, but it's unlikely.

The defaults formatter just changes:

def _get_help_string(self, action):
    help = action.help
    if '%(default)' not in action.help:
        if action.default is not SUPPRESS:
            defaulting_nargs = [OPTIONAL, ZERO_OR_MORE]
            if action.option_strings or action.nargs in defaulting_nargs:
                help += ' (default: %(default)s)'
    return help

You could get the same effect by just including %(default)s in all of your argument help lines. In contrast to the Raw subclasses, this is just a convenience class. It doesn't give you more control over the formatting.

hpaulj
  • 221,503
  • 14
  • 230
  • 353