Objective

Learn to use the argparse module to easily parse python scripts parameters

Requirements

  • Basic knowledge of python and object oriented concepts

Difficulty

EASY

Conventions

  • # - requires given command to be executed with root privileges either directly as a root user or by use of sudo command
  • $ - given command to be executed as a regular non-privileged user
python_logo

Introduction

In a previous article we have seen how to parse command line arguments using getopts in the context of bash scripts (you can find the article here). Now we will see how to accomplish the same task, in a more powerful way, when writing a python script.

Initialize the parser

To achieve our goal, we are going to use a python module called argparse. It will let us specify our command line parameters, and will automatically generate the script help message based on them. So let's begin, we will call our script "printerscript.py":
#!/usr/bin/env python
import argparse

if __name__ == '__main__':
    # Initialize the parser
    parser = argparse.ArgumentParser(
        description="simple script to demonstrate argparse usage"
    )
The first thing to do is, obviously, to import the argparse module. After that we proceed to initialize the parser. The description keyword, passed to the parser constructor is optional, but allows us to add a brief description of the script when the help message is displayed.

There are other keywords we can use to further customize the behavior of the parser: for example providing the epilog keyword we can provide a text to be displayed after the main help message, or by using prog we can specify the name of the program to be displayed in the same context (by default sys.argv[0] is used).

Adding a positional parameter

Now it's time to add our first positional parameter to the script. In this case we will add the parameter "printme", that is the string that will be printed by our test script. We accomplish this by using the add_argument() method of the parser object we initialized above:
parser.add_argument('printme', help="The string to be printed")
The first argument we provided to the method is the name of the parameter, and the second one, optional, is help. Using this keyword, we can specify the description for the parameter that will be displayed in the help message generated by argparse.

It's important to notice that by default the parameters will be considered as strings: to specify another data type, we must use the type keyword. For example, if we wanted our argument to be converted to an integer, we would have specified it this way:
parser.add_argument('printme', type=int)
Once we added our parameter, we must invoke the parse_args() method of the parser object. This method will return an instance of the argparse.Namespace class: the parsed parameters will be stored as attributes of this instance. Finally we can add a line to print the variable. At this point the script should look this way:
#!/usr/bin/env python
import argparse

if __name__ == '__main__':
    # Initialize the parser
    parser = argparse.ArgumentParser(
        description="simple script to demonstrate argparse usage"
    )

    # Add the positional parameter
    parser.add_argument('printme', help="The string to be printed")

    # Parse the arguments
    arguments = parser.parse_args()

    # Finally print the passed string
    print(arguments.printme)
Let's execute it:
$ ./printerscript.py "hello world!"
hello world!
The string we passed has been printed has expected. But what if we didn't provide it? The help message would have been showed, describing the script correct usage:
$ ./printerscript.py
usage: printerscript.py [-h] printme
printerscript.py: error: too few arguments

Adding an optional parameter

Optional parameters are not mandatory for the usage of the script, but they are used to modify its behavior. Argparse recognizes them when it sees that hyphens are provided in the description, so for example:
parser.add_argument(
    '-r', '--repeat',
    help="number of times to print the string",
    type=int,
    default=1
)
The name of the parameter is prefixed with hyphens (we can specify both the short and the long parameter version). In this case we added the optional parameter --repeat which specifies how many times the string must be printed. We also used the default keyword. This is really important, because through it, we can specify the value the attribute will assume if the parameter it's not explicitly provided when calling the script.

At this point, to verify that the parameter works as expected, all we have to do is modify our script in order to repeat the printing of the string for the specified number of times, therefore we enclose the print() function into a little for loop:
for i in range(0, arguments.repeat):
    print(arguments.printme)
Let's try it:
$ ./printerscript.py --repeat=3 "hello world!"
hello world!
hello world!
hello world!
All went as expected. In addition, the help message has also been updated, and now includes a description of the new optional parameter:
./printerscript.py --help
usage: printerscript.py [-h] [-r REPEAT] printme

simple script to demonstrate argparse usage

positional arguments:
  printme               The string to be printed

optional arguments:
  -h, --help            show this help message and exit
  -r REPEAT, --repeat REPEAT
                        number of times to print the string
As said above, when argparse sees that a parameter is prefixed with hyphens, it assumes that it is optional. To modify this behavior and "declare" it as mandatory, we can use the required keyword when adding the parameter, in the form: required=True.

The "dest" keyword

Normally the value provided for a parameter will be stored as an attribute named after the first argument given to the add_argument() method in the case of positional parameters, or the first long string option (with the hyphens removed: the --repeat string will become the 'repeat' attribute) in the case of optional parameters. In the latter case, if a long string option is not available, the short one is used. The dest keyword allows us to specify a custom attribute name instead of relying on this behavior.

The "action" keyword

When using the add_argument() method we can specify the behavior to use for the parsed options by using another keyword: action. The default action is to assign the passed value to the corresponding attribute. In the case of our tiny script, for example, the value provided for the --repeat parameter, will be assigned to the 'repeat' attribute of the argparse.Namespace class once the arguments are parsed. This behavior, however, can also be modified. Let's describe the other main options in brief:
store_true and store_false
By specifying this action, we are basically saying that the parameter doesn't require an argument: True will be assigned as the value to the corresponding attribute if the option is provided, or False otherwise. store_true and store_false will provide respectively a default value of True and False.
store_const
This is similar to the option above, but by using it as a value for the action keyword, instead of a Boolean, a constant value will be assigned to the attribute if the parameter is used. This value is specified itself by using the const keyword:
parser.add_argument("--random-option", action="store_const", const=yourvalue)
append
If append is used as value of the action keyword, a list will be created and referenced by the corresponding parameter attribute: the provided value will be appended to it. This is useful in case the parameter is provided more than once:
parser.add_argument('--random-option', action="append")
append_const
Just like when using append, a value will appended to the list referenced by the parameter attribute. The difference is that in this case, the value is not provided by the user, but declared when adding the parameter, again, via the const keyword:
parser.add_argument(
    '--randomoption',
    action="append_const",
    const="the value to append"
)

Mutually exclusive optional parameters

In certain situation we may need to make some options mutually exclusive. The argparse module lets us accomplish this task in a vary easy way. Basically what we are going to do is to create a separate group of options using the add_mutually_exclusive_group() method of the parser object, and add our arguments to it. For example:
parser = argparse.ArgumentParser();

# create our group of mutually exclusive arguments
mutually_exclusive = parser.add_mutually_exclusive_group()
mutually_exclusive.add_argument("--foo", help="foo excludes bar")
mutually_exclusive.add_argument("--bar", help="bar excludes foo")
One thing to notice is that to be part of a mutually_exclusive_group arguments must be optional, therefore positional arguments, or arguments you defined as required (required=True) are not allowed in it.

At this point you should have an idea of how argparse works. However, we only scratched the surface of what this module has to offer: for a complete description of all its functionalities please go ahead and read the documentation, you won't regret it. Nice scripting!