Ben Chuanlong Du's Blog

And let it direct your passion with reason.

Hands on the Python Module argparse

  1. argparse is the best library to use to parse command-line arguments in Python. It is included in Python standard libaries (which menas that you can use it out of the box).

  2. It is suggestedd that you always log a parsed Namespace object so that you can check whether it is as expected.

  3. ArgumentParser.parse_args takes a list (instead of string) for the parameter args! This is due to sys.argv is of the list type.

  4. The add_parser function can be used to define subcommands. It takes both a help argument and a description argument. The help argument is for help doc of its parent command while the description argument is for help doc of itself. It is suggested that you pass the same help doc to both arguments. Please refer to /blog.py for such examples.

  5. The argument aliases does not take a geneartor as input. Generally speaking, you should be carefule about using a generator as a generator is essentially an iterator which is invalidated once iterated. Use a list instead if you have to iterator a collection multiple times.

  6. It seems that the default value for an argument must be specified in the first occurrence of the corresponding add_argument function.

  7. You can check whether an option is defined for a command or not using 'some_option' in args where args is a Namespace object returned by argparse.parse_args. For example, you can use args.level if 'level' in args else 'INFO' to get the value for the option args.level with the fallback value INFO. You can also convert a Namespace object to a dictionary using the function vars, so an even easier way of get the value of an option with a fallback value is use vars(args).get('level', 'INFO'). One thing to notice that if an option belongs to a mutual exclusive group which is required, then it is ALWAYS included in the final parsed Namespace object. In that case, the right way to check whether the option is specified is to check whether it is None or not. Please refer to the Mutually Exclusive section for more discussions.

  8. Do NOT call time-consuming or likely-to-throw-exception functions/methods when defining default values of command-line options!!! Specially, avoid calling HTTP request to parse information for default values of command-line options. The reason is that default values for options are always calculated no matter it is needed for the (sub)command or not. If a function/method which is time-consuming, or likely to throw exception, or might fail due to firewall is used to define default values of command-line options, it greatly hurts use experience of the command-line tool. A better alternative is to use None, empty string, etc. for the default value and handle it when the corresponding (sub)command is invoked. This delays the computation of the function/method until it is really needed.

Optional Positional Arguments

By design, positional arguments are always required (which is different from options). However, you can leverage the nargs option to achive optional positional arguments. Basically, you use nargs=* to let argparse knwo that the positonal argument takes 0 or more inputs.

In [1]:
from argparse import ArgumentParser, Namespace
In [2]:
parser = ArgumentParser(description="Illustrate an optional positional argument.")
parser.add_argument("numbers", nargs="*", help="A list of numbers.")
Out[2]:
_StoreAction(option_strings=[], dest='numbers', nargs='*', const=None, default=None, type=None, choices=None, help='A list of numbers.', metavar=None)
In [3]:
parser.parse_args([])
Out[3]:
Namespace(numbers=[])
In [4]:
parser.parse_args(["1", "2", "3"])
Out[4]:
Namespace(numbers=['1', '2', '3'])
In [5]:
parser.parse_args("1 2 3")
Out[5]:
Namespace(numbers=['1', ' ', '2', ' ', '3'])

Convert to dict

In [5]:
ns = Namespace(x=1, y=2)
ns
Out[5]:
Namespace(x=1, y=2)
In [6]:
vars(ns)
Out[6]:
{'x': 1, 'y': 2}

Convert from dict

In [7]:
dic = {"x": 1, "y": 2}
dic
Out[7]:
{'x': 1, 'y': 2}
In [8]:
Namespace(**dic)
Out[8]:
Namespace(x=1, y=2)

Mutually Exclusive

You can use the method add_mutually_exclusive_group to define a mutually exclusive group, i.e., only one option in the group can be specified in the command line. And you use the option required=True to make the group required, i.e., one of the options in the group must be specified.

Notice that all options in a REQUIRED mutually exclusive group are present in the final parsed Namespace. Those options not specified have a default value None. So the right way to check whether an option in a mutually exclusive group is specified is to check whether it has value other than None. That is use the following code

:::bash
if args.some_mutex_opt is not None:
    ...

instead of

:::bash
if "some_mutex_opt" in args:
    ...
In [ ]:
mutex_group = parser.add_mutually_exclusive_group()
In [ ]:
mutex_group = parser.add_mutually_exclusive_group(required=True)
In [ ]:
 

Comments