Using Python’s argparser

1 minute read

In this post I am sharing best practises on using Python’s argparser. This is common feedback I have given in pull request reviews over the past few years.

Why would you care:

  • Creating meaningful argument parsing is a skill. It can really make a difference on how useful your code is and how easily others can use it.

There are at least two main common error patterns I observed:

  • How to pass booleans
  • How to pass lists Both are very reasonable things to want to do; each gets a small section below.

Booleans

Often we want to pass boolean arguments to control execution. A very frequent pattern is this one:

parser.add_argument("--create-report",
                    type=bool,        
                    default=True,
                    help="True if you want to write a report, False otherwise.")                            

This simply does not work. To convince yourself:

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--my_bool", type=bool)
cmd_line = ["--my_bool", "False"]
print(parser.parse_args(cmd_line))
# returns Namespace(my_bool=True)

How to do it StackOverflow reference:

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--my_bool',  action='store_true')

cmd_line = ["--my_bool" ]
print(parser.parse_args(cmd_line))
# returns Namespace(my_bool=True)
cmd_line = []
print(parser.parse_args(cmd_line))
# returns Namespace(my_bool=False)

Lists

Another reasonable need is to want to pass lists. Example:

parser.add_argument("--my-categories",
                    type=list,        
                    default=['005', '00T'],
                    help="List of categories to filter data.")    

This simply does not work. To convince yourself:

parser = argparse.ArgumentParser()
parser.add_argument("--my-categories", type=list)
cmd_line = ["--my-categories", "['005', '500']"]
print(parser.parse_args(cmd_line))
# returns Namespace(my_categories=['[', "'", '0', '0', '5', "'", ',', ' ', "'", '5', '0', '0', "'", ']'])

or you may be tempted to do:

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--my-categories", type=str )
cmd_line = ["--my-categories", "['005', '500']"]
print(parser.parse_args(cmd_line))
# returns Namespace(my_categories="['005', '500']")
# then you go and apply ast.literal_eval

But this is not good separation of concerns. argparser can do argument validation for you for example. And you can achieve this out-of-the-box.

How to do it StackOverflow reference

# this is test.py
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--my-categories", 
                    nargs='+', type=str) # type: refers to each elems type
args = parser.parse_args()
print(args.my_categories)

And here is how it works:

python test.py --my-categories 005 006 007
# ['005', '006', '007']