Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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()
like image 359
jpyams Avatar asked Oct 12 '17 22:10

jpyams


People also ask

Should I use Argparse?

If you plan to be a software developer with Python, you'll want to be able to use argparse for your scripting needs. If you're a data scientist, you'll likely find yourself needing to port your code from a Jupyter Notebook to a reproducible script.

What does Nargs do in Argparse?

Using the nargs parameter in add_argument() , you can specify the number (or arbitrary number) of inputs the argument should expect. In this example named sum.py , the --value argument takes in 3 integers and will print the sum.

What is Store_true Argparse?

The store_true option automatically creates a default value of False. Likewise, store_false will default to True when the command-line argument is not present. The source for this behavior is succinct and clear: http://hg.python.org/cpython/file/2.7/Lib/argparse.py#l861.

Where do you put Argparse?

It's fine to put the import argparse within the if __name__ == '__main__' block if argparse is only referred to within that block.


2 Answers

There is nothing wrong with your code, that's just the consequence of using the argparse module. My personal preference is to break up the creation of the parser into functions. In this case, you can create a function for each subparser you create.

def parse_args(args=sys.argv[1:]):
    parser = argparse.ArgumentParser(description='A nontrivial modular command')
    subparsers = parser.add_subparsers(help='sub-command help')

    add_load_subparser(subparsers)
    add_write_subparser(subparsers)
    add_save_subparser(subparsers)

    return parser.parse_args(args)


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


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


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


args = parse_args()
like image 148
Aldehir Avatar answered Nov 15 '22 23:11

Aldehir


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()
like image 31
mkrieger1 Avatar answered Nov 16 '22 01:11

mkrieger1