What's the best way to parse command line arguments?

290,585

Solution 1

This answer suggests optparse which is appropriate for older Python versions. For Python 2.7 and above, argparse replaces optparse. See this answer for more information.

As other people pointed out, you are better off going with optparse over getopt. getopt is pretty much a one-to-one mapping of the standard getopt(3) C library functions, and not very easy to use.

optparse, while being a bit more verbose, is much better structured and simpler to extend later on.

Here's a typical line to add an option to your parser:

parser.add_option('-q', '--query',
            action="store", dest="query",
            help="query string", default="spam")

It pretty much speaks for itself; at processing time, it will accept -q or --query as options, store the argument in an attribute called query and has a default value if you don't specify it. It is also self-documenting in that you declare the help argument (which will be used when run with -h/--help) right there with the option.

Usually you parse your arguments with:

options, args = parser.parse_args()

This will, by default, parse the standard arguments passed to the script (sys.argv[1:])

options.query will then be set to the value you passed to the script.

You create a parser simply by doing

parser = optparse.OptionParser()

These are all the basics you need. Here's a complete Python script that shows this:

import optparse

parser = optparse.OptionParser()

parser.add_option('-q', '--query',
    action="store", dest="query",
    help="query string", default="spam")

options, args = parser.parse_args()

print 'Query string:', options.query

5 lines of python that show you the basics.

Save it in sample.py, and run it once with

python sample.py

and once with

python sample.py --query myquery

Beyond that, you will find that optparse is very easy to extend. In one of my projects, I created a Command class which allows you to nest subcommands in a command tree easily. It uses optparse heavily to chain commands together. It's not something I can easily explain in a few lines, but feel free to browse around in my repository for the main class, as well as a class that uses it and the option parser

Solution 2

argparse is the way to go. Here is a short summary of how to use it:

1) Initialize

import argparse

# Instantiate the parser
parser = argparse.ArgumentParser(description='Optional app description')

2) Add Arguments

# Required positional argument
parser.add_argument('pos_arg', type=int,
                    help='A required integer positional argument')

# Optional positional argument
parser.add_argument('opt_pos_arg', type=int, nargs='?',
                    help='An optional integer positional argument')

# Optional argument
parser.add_argument('--opt_arg', type=int,
                    help='An optional integer argument')

# Switch
parser.add_argument('--switch', action='store_true',
                    help='A boolean switch')

3) Parse

args = parser.parse_args()

4) Access

print("Argument values:")
print(args.pos_arg)
print(args.opt_pos_arg)
print(args.opt_arg)
print(args.switch)

5) Check Values

if args.pos_arg > 10:
    parser.error("pos_arg cannot be larger than 10")

Usage

Correct use:

$ ./app 1 2 --opt_arg 3 --switch

Argument values:
1
2
3
True

Incorrect arguments:

$ ./app foo 2 --opt_arg 3 --switch
usage: convert [-h] [--opt_arg OPT_ARG] [--switch] pos_arg [opt_pos_arg]
app: error: argument pos_arg: invalid int value: 'foo'

$ ./app 11 2 --opt_arg 3
Argument values:
11
2
3
False
usage: app [-h] [--opt_arg OPT_ARG] [--switch] pos_arg [opt_pos_arg]
convert: error: pos_arg cannot be larger than 10

Full help:

$ ./app -h

usage: app [-h] [--opt_arg OPT_ARG] [--switch] pos_arg [opt_pos_arg]

Optional app description

positional arguments:
  pos_arg            A required integer positional argument
  opt_pos_arg        An optional integer positional argument

optional arguments:
  -h, --help         show this help message and exit
  --opt_arg OPT_ARG  An optional integer argument
  --switch           A boolean switch

Solution 3

Using docopt

Since 2012 there is a very easy, powerful and really cool module for argument parsing called docopt. Here is an example taken from its documentation:

"""Naval Fate.

Usage:
  naval_fate.py ship new <name>...
  naval_fate.py ship <name> move <x> <y> [--speed=<kn>]
  naval_fate.py ship shoot <x> <y>
  naval_fate.py mine (set|remove) <x> <y> [--moored | --drifting]
  naval_fate.py (-h | --help)
  naval_fate.py --version

Options:
  -h --help     Show this screen.
  --version     Show version.
  --speed=<kn>  Speed in knots [default: 10].
  --moored      Moored (anchored) mine.
  --drifting    Drifting mine.

"""
from docopt import docopt


if __name__ == '__main__':
    arguments = docopt(__doc__, version='Naval Fate 2.0')
    print(arguments)

So this is it: 2 lines of code plus your doc string which is essential and you get your arguments parsed and available in your arguments object.

Using python-fire

Since 2017 there's another cool module called python-fire. It can generate a CLI interface for your code with you doing zero argument parsing. Here's a simple example from the documentation (this small program exposes the function double to the command line):

import fire

class Calculator(object):

  def double(self, number):
    return 2 * number

if __name__ == '__main__':
  fire.Fire(Calculator)

From the command line, you can run:

> calculator.py double 10
20
> calculator.py double --number=15
30

Solution 4

The new hip way is argparse for these reasons. argparse > optparse > getopt

update: As of py2.7 argparse is part of the standard library and optparse is deprecated.

Solution 5

I prefer Click. It abstracts managing options and allows "(...) creating beautiful command line interfaces in a composable way with as little code as necessary".

Here's example usage:

import click

@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.option('--name', prompt='Your name',
              help='The person to greet.')
def hello(count, name):
    """Simple program that greets NAME for a total of COUNT times."""
    for x in range(count):
        click.echo('Hello %s!' % name)

if __name__ == '__main__':
    hello()

It also automatically generates nicely formatted help pages:

$ python hello.py --help
Usage: hello.py [OPTIONS]

  Simple program that greets NAME for a total of COUNT times.

Options:
  --count INTEGER  Number of greetings.
  --name TEXT      The person to greet.
  --help           Show this message and exit.
Share:
290,585

Related videos on Youtube

kamens
Author by

kamens

Lead dev at Khan Academy. Ex-VP of Engineering at Fog Creek. @kamens

Updated on August 08, 2020

Comments

  • kamens
    kamens over 3 years

    What's the easiest, tersest, and most flexible method or library for parsing Python command line arguments?

  • matt wilkie
    matt wilkie about 13 years
    This answer is wonderfully clear and easy to follow -- for python 2.3 thru 2.6. For python 2.7+ it is not the best answer as argparse is now part of the standard library and optparse deprecrated.
  • telotortium
    telotortium about 11 years
    Thank you. This script helped me work out some really complicated quoting I needed to do when passing startup commands to GVim.
  • shuttle87
    shuttle87 almost 10 years
    With the deprecation of getopt in newer versions of Python this answer has gone out of date.
  • Christophe Roussy
    Christophe Roussy over 8 years
    This is very concise and useful and here is the official doc (for convenience): docs.python.org/3/library/argparse.html
  • keen
    keen almost 8 years
    how does docopt "need no installation" ? it's a python module so it does have to be installed. 'ImportError: No module named docopt'
  • ndemou
    ndemou almost 8 years
    @keen it's not included with python for sure but you don't need to install it: "you can just drop docopt.py file into your project--it is self-contained" -- github.com/docopt/docopt
  • keen
    keen almost 8 years
    we just have different definitions of install - and I wanted to point that out for future readers.
  • ndemou
    ndemou almost 8 years
    @keen I've added a note on "no installation" for people sharing your definition :-)
  • krassowski
    krassowski over 6 years
    I've developed a small package utilizing automatic arguments creation: declarative_parser. Of course, if one is working with werkzeug, it may be better to keep the werkzung.script. Anyway, I'm a huge fan of such approach.
  • krassowski
    krassowski over 6 years
    I've used similar approach in declarative-parser, see arguments deduction (typing, docstrings, kwargs) in docs. Main differences: python3, type hints, pip-installable.
  • historystamp
    historystamp about 6 years
    the quotes are mismatched in the def statement.
  • DisappointedByUnaccountableMod
    DisappointedByUnaccountableMod over 5 years
    Point of information: the neatest use of plac (as shown in the example) is for Python 3.x only, because it uses 3.x function annotations.
  • Skippy le Grand Gourou
    Skippy le Grand Gourou about 5 years
    @shuttle87 As of python3.7.2, getopt is still not deprecated… But its documentation states that it is mainly provided for users familiar with the C getopt() function, and acknowledges that for other users argparse might be a better solution, allowing to "write less code and get better help and error messages".
  • Joe Holloway
    Joe Holloway over 4 years
    Your main link is 404 so I replaced it with a link to a SO question that addresses the same topic.
  • Boris Verkhovskiy
    Boris Verkhovskiy over 4 years
    Last commit in 2012
  • Yogeshwar Singh
    Yogeshwar Singh over 4 years
    In my case, I want to profile my application to detect the slowness. There is another tool called [tuna] (github.com/nschloe/tuna) which allows me to profile the entire application by simply adding agrs -mcProfile -o program.prof but agrparcer is capturing these args, how to I pass these args to python exe???
  • Nimitz14
    Nimitz14 over 4 years
    If you find argparse too verbose use plac instead.
  • jkulhanek
    jkulhanek over 2 years
    You can also try github.com/jkulhanek/aparse which extends argparse or click with support for typing.
  • j.c
    j.c about 2 years
    @nimitz14: the plac link you provided is not working.
  • Nimitz14
    Nimitz14 about 2 years