argparse

If you recall from the "Writing Scripts" page, we mentioned that ordering arguments is the easiest way to write your scripts. Unfortunately, this is limited in its functionality — other people might not know of your forced order and it can be complicated when many arguments are required.

argparse is a Python package that solves our problems by creating consistent command-line interfaces and handling the arguments necessary to make your script function.


Coding Basics

We’ll begin by showing argparse stripped down to its most basic level. The contents of this tutorial were inspired by Python’s argparse tutorial and the argparse documentation.


import argparse


def main():
    parser = argparse.ArgumentParser()
    args = parser.parse_args()


if __name__ == '__main__':
    main()

Recall that the structure of main and the conditional are features of scripts and are only necessary for running scripts, not for understanding argparse.

As with any package, the first step is importing argparse. Our parser variable is an ArgumentParser object, on which we add & parse arguments and ask for help. Assigning parser.parse_args() to args is the final step that checks for errors and converts our argument strings into objects. Declaring that variable is recommended to access the arguments for further use in the script.

If the above code was stored in a file called test.py and then executed in the terminal, it wouldn’t yield anything. However, we can include the -h/--help flag to get some basic output:

$ python test.py -h
usage: test.py [-h]

optional arguments:
  -h, --help  show this help message and exit

The help flag is intrinsic to any script and is very useful for deciphering any script you use or write.


Arguments & Options

Let’s make our script more useful by adding some arguments before parsing:

import argparse


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('say', help='outputs the given string')
    parser.add_argument('boom', help='prints the string in uppercase')
    args = parser.parse_args()
    print(args.say)
    print(args.boom.upper())


if __name__ == '__main__':
    main()
$ python test.py hey ya
hey
YA

Let’s use --help again to determine what we’re looking at:

$ python test.py --help
usage: test.py [-h] say boom

positional arguments:
  say         outputs the given string
  boom        prints the string in uppercase

optional arguments:
  -h, --help  show this help message and exit

Cool! We confirm that say and boom are positional arguments, meaning that their position is specific and required. Our optional help parameter of the add_argument method is displayed in the help menu. If we don’t include arguments, we get the error message test.py: error: the following arguments are required: and the missing arguments are listed.

Our help menu gives us some nice hints regarding the difference between arguments and options. As the name suggests, options are optional arguments that can be used to augment or change the function of the script. Option and flags are used interchangeably to describe the same thing. There are short forms starting with - and long forms starting with --; both do the same thing, but short forms are abbreviated versions of long forms and can help make commands shorter.

How about we add some arguments to change the function of our script:

import argparse


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('say', help='outputs the given string')
    parser.add_argument('boom', help='prints the string in uppercase')
    parser.add_argument('-sho', '--shout', help='yells both inputs', action='store_true')
    parser.add_argument('-shu', '--shush', help='quiets both inputs', action='store_true')
    args = parser.parse_args()
    if args.shout & args.shush:
        print('Cancelled out')
        print(args.say)
        print(args.boom.upper())
    elif args.shout:
        print(args.say.upper())
        print(args.boom.upper())
    elif args.shush:
        print(args.say)
        print(args.boom)
    else:
        print(args.say)
        print(args.boom.upper())


if __name__ == '__main__':
    main()
$ python test.py boo yah -sho
BOO
YAH
$ python test.py uh oh -shu
uh
oh
$ python test.py take that -shu -sho
Cancelled out
take
THAT

You might notice that we added action as a parameter to add_argument for both of our new flags. Setting action to "store_true" makes the flag True if included and False if not. Most flags include this parameter, as excluding it requires additional input, which is unnecessary if the script is changed depending only on the flag’s inclusion.


In-depth Improvements

The information above is enough for general scripts to be written, but there are some specifics that can enhance our programming further.


Mutually-Exclusive Groups

Let’s start by modifying test.py. --shout and --shush perform opposite functions and, based on how we’ve written the script, cancel each other out. Including both is unintuitive and muddles our script — let’s utilize the add_mutually_exclusive_group() function included in ArgumentParser().

import argparse


def main():
    parser = argparse.ArgumentParser()
    changer = parser.add_mutually_exclusive_group()
    parser.add_argument('say', help='outputs the given string')
    parser.add_argument('boom', help='prints the string in uppercase')
    changer.add_argument('-sho', '--shout', help='yells both inputs', action='store_true')
    changer.add_argument('-shu', '--shush', help='quiets both inputs', action='store_true')
    args = parser.parse_args()
    if args.shout:
        print(args.say.upper())
        print(args.boom.upper())
    elif args.shush:
        print(args.say)
        print(args.boom)
    else:
        print(args.say)
        print(args.boom.upper())


if __name__ == '__main__':
    main()
$ python test.py -h
usage: test.py [-h] [-sho | -shu] say boom

positional arguments:
  say            outputs the given string
  boom           prints the string in uppercase

optional arguments:
  -h, --help     show this help message and exit
  -sho, --shout  yells both inputs
  -shu, --shush  quiets both inputs

We see from the help menu under usage that the second flag is [-sho | -shu], indicating that you use one flag or the other. If we include both --shout and --shush in one command, we get the error message test.py: error: argument -shu/--shush: not allowed with argument -sho/--shout.


Dealing with integers and choices

For the next demonstration, we’ll move on from test.py and create a new Python script called numbers.py. The goal of this script will be to take the sum of 3 integers, then multiply it by some integer indicated by a flag.

import argparse


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('x', type=int, help='first value')
    parser.add_argument('y', type=int, help='second value')
    parser.add_argument('z', type=int, help='third value')
    parser.add_argument('-f', '--factor', type=int, help='value by which the sum is multiplied', default=1)
    args = parser.parse_args()
    print((args.x + args.y + args.z) * args.factor)


if __name__ == '__main__':
    main()
$ python numbers.py 1 2 3
6

You might have noticed we added the type and default parameters of add_argument. Without these two, our script would not function as desired:

  • For argparse, the default value for type is string. Leaving out type=int would result in string concatenation instead of addition. The type argument must be changed if you want to deal with anything other than strings.

  • If a flag is missing, the default is None, even if the flag is an integer. Leaving out default=1 here would result in the error message TypeError: unsupported operand type(s) for *: 'int' and 'NoneType'

If we wanted to adopt a "less is more" philosophy for the script and maximize its multiplication at 3, we could use the choices parameter as follows:

import argparse


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('x', type=int, help='first value')
    parser.add_argument('y', type=int, help='second value')
    parser.add_argument('z', type=int, help='third value')
    parser.add_argument('-f', '--factor', type=int, help='value by which the sum is multiplied',
                        default=1, choices = [1, 2, 3])
    args = parser.parse_args()
    print((args.x + args.y + args.z) * args.factor)


if __name__ == '__main__':
    main()
$ python numbers.py 1 2 3 -f 2
12
$ python numbers.py 1 2 3 -f 4
usage: numbers.py [-h] [-f {1,2,3}] x y z
numbers.py: error: argument -f/--factor: invalid choice: 4 (choose from 1, 2, 3)

As shown above, our options are limited to tripling, doubling, or keeping the sum. Adapting choices is a very straightforward process that can be very powerful for catching edge cases that might otherwise break your script.