Search code examples
pythonargparsecode-formatting

Best practices for writing argparse parsers


Are there best practices or style guidelines for working with Python's argparse module?

I work with argparse on a regular basis, and it quickly takes up a respectable number of lines to handle all the configuration. For almost everything I find that sticking close to PEP 8 results in clean, readable code, but not here. The end result is always an ugly block of code that is painful to read.

Painful to read is not Pythonic:

Beautiful is better than ugly ... Readibilty counts

So is there a PEP or some other resource that provides guidelines for how to better format this code?

A sample of the ugliness (mostly following PEP 8):

parser = argparse.ArgumentParser(description='A nontrivial modular command')
subparsers = parser.add_subparsers(help='sub-command help')

parser_load = subparsers.add_parser('load', help='Load something somewhere')
parser_load.add_argument('--config',
                         help='Path to configuration file for special settings')
parser_load.add_argument('--dir', default=os.getcwd(),
                         help='The directory to load')
parser_load.add_argument('book', help='The book to load into this big thing')
parser_load.add_argument('chapter', nargs='?', default='',
                         help='Optionally specify a chapter')
parser_load.add_argument('verse', nargs='*',
                         help='Optionally pick as many verses as you want to'
                         ' load')
parser_load.set_defaults(command='load')

parser_write = subparsers.add_parser(
                'write', help='Execute commands defined in a config file')
parser_write.add_argument('config', help='The path to the config file')
parser_write.set_defaults(command='write')

parser_save = subparsers.add_parser(
                'save',
                help='Save this big thing for use somewhere later')
parser_save.add_argument('-n', '--name', default=None,
                         help='The name of the component to save')
parser_save.add_argument('path', help="The way out of Plato's cave")
parser_save.set_defaults(command='save')

...

args = parser.parse_args()

Solution

  • As commented by TemporalWolf, I would use line breaks more consistently, and more of them. Even if the code now appears longer, I find it easier to read:

    • More vertical space between individual function calls, therefore easier to distinguish visually
    • One argument per line, therefore easier to see which ones are used
    • Arguments closer to the left margin, therefore less horizontal eye movement and fewer unwanted line breaks (like the one where you split the help string) required

    Additionally, by renaming parser_X/parser_YX_parser/Y_parser you could make it easier to distinguish X/Y.

    parser = argparse.ArgumentParser(
        description='A nontrivial modular command'
    )
    subparsers = parser.add_subparsers(
        help='sub-command help'
    )
    
    load_parser = subparsers.add_parser(
        'load',
        help='Load something somewhere'
    )
    load_parser.add_argument(
        '--config',
        help='Path to configuration file for special settings'
    )
    load_parser.add_argument(
        '--dir',
        default=os.getcwd(),
        help='The directory to load'
    )
    load_parser.add_argument(
        'book',
        help='The book to load into this big thing'
    )
    load_parser.add_argument(
        'chapter',
        nargs='?',
        default='',
        help='Optionally specify a chapter'
    )
    load_parser.add_argument(
        'verse',
        nargs='*',
        help='Optionally pick as many verses as you want to load'
    )
    load_parser.set_defaults(
        command='load'
    )
    
    write_parser = subparsers.add_parser(
        'write',
        help='Execute commands defined in a config file'
    )
    write_parser.add_argument(
        'config',
        help='The path to the config file'
    )
    write_parser.set_defaults(
        command='write'
    )
    
    save_parser = subparsers.add_parser(
        'save',
        help='Save this big thing for use somewhere later'
    )
    save_parser.add_argument(
        '-n', '--name',
        default=None,
        help='The name of the component to save'
    )
    save_parser.add_argument(
        'path',
        help="The way out of Plato's cave"
    )
    save_parser.set_defaults(
        command='save'
    )
    
    ...
    
    args = parser.parse_args()