Command Line Arguments with Python's Argparse Module
Processing command-line arguments in ad-hoc python tools is one of those areas where I tend to just hack it together from scratch — simply because the effort of learning and understanding the relevant library packages not only seems to be more work than it is worth, but also and in particular more effort than “just doing it” by hand. I don’t want anything fancy, after all. I just want to get it done.
Python’s argparse
module is certainly intimidating in this regard:
the official Python API reference documentation includes a pointer to
an equally official tutorial/howto document — clearly, nobody is
expected to understand the API from the reference alone. That does
not seem to bode well.
So it came as a considerable surprise to discover that argparse
is
amazingly convenient. Here is how it works:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument( 'val', help="text" ) # positional arg
parser.add_argument( '-v', '--verbose', help="text" ) # optional arg
args = parser.parse_args()
print( args.val )
print( args.verbose )
That’s it. That’s shorter than any ad-hoc code I could come up with. And that’s not even mentioning any of the under-the-covers functionality that’s provided for free. (See below.)
The argparse
module makes a distinction between positional and
optional arguments. Positional arguments are generally required
and are not preceded by command-line flags. (Think of the file or
directory names passed to such Unix commands as mv
or cp
.)
Positional command line arguments will be assigned in the order in
which they were defined using add_argument()
.
Optional arguments are prefixed by one or two dashes. The argparse
module considers arguments as positional or optional automatically,
based on the presence or absence of that prefix. Somewhat surprisingly,
positional and optional arguments can be freely intermingled on the
actual command line: it is not necessary for positional arguments to
always come last.
Much useful functionality can be accessed through keyword arguments
to add_argument()
. Here are some of the more useful ones:
help
: Usage information for the argument.type
: Automatic type conversion (e.g.type=int
). The value must be a conversion function from string to the desired data type.choices
: An array of valid values (e.g.choices=[1,2,3]
).default
: A default value, if argument is missing (e.g.default=0
).required
: UnlessTrue
, the option is optional.nargs
: The number of arguments to consume. The arguments will be returned as a list. Its value is usually an integer, or"?"
,"*"
, or"+"
to indicate 0 or 1, 0 or all, or at least 1 or all.action
: What to do with the argument. The default is"store"
, meaning that the value is stored in the object returned byparse_args()
. Other possibilities include"count"
, which returns the number of times the option has been given on the command line, and a few others (see below).
In addition to parsing the command-line arguments, the argparse
module
also performs some input validation and constraint enforcement, as is
evident from the choices
and required
keywords. If input does not
meet the expected constraints, then argparse
automatically prints a
neatly formatted and rather verbose usage message to standard error.
In addition to the help
keyword, there are additional arguments that
can be used to customize the content and format of that message. The
option -h
(or --help
) is provided by default and prints the usage
information. (Supply add_help=False
to the ArgumentParser()
constructor to disable this behavior.)
The two keywords that may not be immediately clear are nargs
and
action
. If nargs
is set to an integer, then the corresponding
option will consume exactly that many parameters from the command
line, and will fail (printing a usage message) if either fewer or
more arguments are provided. The symbolic multiplier arguments are
available to deal with a variable number of arguments:
add_argument( 'files', nargs="+" )
expects at least one, but
possibly several arguments, and will store them, as a list, in
the files
attribute. (The module is fairly smart about edge
cases, such as an argument with nargs="*"
being followed
by another required positional argument and so on. Check the
reference documentation or experiment yourself.)
The action
keyword indicates what should be done with the
received command line argument. By default (action="store"
),
the value provided on the command line is simply stored as
attribute on the object returned by parse_args()
. Another
possibility is action="count"
, which stores the number of
times the option was used in the command line: useful to set,
for example, verbosity levels through repeated use of a -v
option.
The two actions "store_true"
and "store_false"
are used to implement
command line switches, that is, command line arguments that don’t
take a value, but work simply by being present or absent. (Think of
rm -f
.) The "store"
action expects an argument, and will fail
if none is provided. By contrast, "store_true"
will store the value
True
if the option is present, and False
otherwise. As an
aside, the same effect could be achieved by using the "count"
action,
and checking whether the count is greater than zero. (Note that the
default value for "count"
is None
, not 0
. Use the default
keyword to set the value to 0
in case the option is absent.)
That’s it. This should cover all situation in which I would have considered hacking up command line handling from scratch.
Of course there is more; the reference documentation has all the
details. With a basic idea of what argparse
is trying to achieve,
and how it goes about it, the reference page actually makes fairly
easy reading.
The main take-away, though, is this. For all the cases that one would consider implementing command line handling from scratch, the following lines of code (properly adapted) will do the trick:
import argparse parser = argparse.ArgumentParser() parser.add_argument( '-d', type=int ) parser.add_argument( '-f', '--force', action="store_true" ) parser.add_argument( '-v', '--verbose', action="count", default=0 ) parser.add_argument( 'things', nargs="*", help="help text" ) args = parser.parse_args() print( args.d, args.force, args.verbose, args.things )
That can’t be beat.
Further reading: