In Python you can retrieve arguments of an script using the sys module.
Arguments are stored in sys.argv[1:], the first argument sys.argv[0] is always
the name of the script.
Let us consider the following code found in python_basics/sys/01_test_sys_argv.py
import sys
if __name__ == "__main__":
print ('Number of arguments:', len(sys.argv), 'arguments.')
print ('Argument List:', str(sys.argv))This code uses sys.argv in order to retrieve the bash commands passed after calling test_sys_argv.py. For example if you run python test_sys_argv.py dog cat you will get the following:
python 01_test_sys_argv.py dog catNumber of arguments: 3 arguments.
Argument List: ['test_sys.py', 'dog', 'cat']Notice that sys.argv is a list. You can use the strings inside this list to define different execution branches inside your python script.
The module argparse from the Python Standard Library allows us to easily write command-line interfaces. To do so, we can define the arguments we expect will be passed when calling a python program. The arguments can be defined as key-word arguments.
args = build_argument_parser()
-
Define
parser = argparse.ArgumentParser(description="High level program description") -
Use
parser.add_argumentto add a new command-line keyword argument.- You can add as many keyword arguments as you want.
- The first argument of
parser.add_argumentis usually used to define a short version of the name of the key in the keyword argument. By convention, short version arguments have a-followed by a single letter. For example-r. - The first argument of
parser.add_argumentdefines a long version of the name of the key in the keyword argument. By convention, long version arguments have a--followed by a word . For example--radius - Arguments can be forced using
required=True. - A help string can be passed to give some information to user
help="what the keyword argument does"
- The type of the keyword argument can be passed with
type=mytype,- For example
type=floatortype=int.
- For example
-
After all keyword arguments are added an
argsobject can be created usingargs = parser.parse_args(). This object will contain the different arguments as variables or fields. The name of the variables will be the name given by the long argument.
Let us make a program that computes the volume of a circle. We want to allow users to pass in the shell the radius an the height of the cylinder. To do so, we can use argpase :
import argparse
import math
def build_argument_parser():
parser = argparse.ArgumentParser(description="Calculate Cylinder Volume")
parser.add_argument('-r', '--radius', type=float, metavar='', required=True, help='Radius of the cylinder')
parser.add_argument('-H', '--height', type=float, metavar='', required=True, help='height of the cylinder')
args = parser.parse_args()
return args
def cylinder_volume(radius, height ):
return math.pi * (radius**2) * height
if __name__ == "__main__":
radius, height = args.radius, args.height
volume = cylinder_volume(args.radius, args.height)
print("Cylinder volume is {}".format(volume))Since the arguments are set with require=True notice that the code cannot be called without them:
python 02_test_argparse.pyusage: 02_test_argparse.py [-h] -r -H
test_argparse.py: error: the following arguments are required: -r/--radius, -H/--heightYou can also notice that a useful message is generated telling the user what arguments are needed and the format required to pass such information. Example of a correct command line call to our program:
python 02_test_argparse.py -r 1 -H 1Cylinder volume is 3.141592653589793The following examples show how you should deal with boolean input times in your command line interface.
This example shows that input argument bools are not correctly understood by argparse if you pass them as "True" or "False".
import argparse
def parse_commandline():
parser = argparse.ArgumentParser(description="Parse inputs")
parser.add_argument('-n', '--name', type=str, required=True, help='Person name')
parser.add_argument('-a', '--age', type=int, required=False, help='Age of the person.')
parser.add_argument( '-ec', '--ever_convicted', type=bool, required=False, help='Boolean stating if the person has been convicted previously.')
args = parser.parse_args()
return args
if __name__ == "__main__":
exp_args = parse_commandline()
print("\tPerson name:", exp_args.name)
print("\tAge:", exp_args.age)
print("\tConvicted:", exp_args.ever_convicted)Notice that this program does not behave as you might expect when using "False"
python 03_problematic_bool.py -n David -a 20 -ec False Person name: David
Age: 20
Convicted: True
python 03_problematic_bool.py -n David -a 20 -ec True Person name: David
Age: 20
Convicted: True
This example shows how can you use a custom method to solve the issue found in 03_problematic_bool.py
import argparse
def str2bool(v):
if isinstance(v, bool):
return v
if v.lower() in ('yes', 'true', 't', 'y', '1'):
return True
elif v.lower() in ('no', 'false', 'f', 'n', '0'):
return False
else:
raise argparse.ArgumentTypeError('Boolean value expected.')
def parse_commandline():
parser = argparse.ArgumentParser(description="Parse inputs")
parser.add_argument('-n', '--name', type=str, required=True, help='Person name')
parser.add_argument('-a', '--age', type=int, required=False, help='Age of the person.')
parser.add_argument( '-ec', '--ever_convicted', type=str2bool, required=False, help='Boolean stating if the person has been convicted previously.')
args = parser.parse_args()
return args
if __name__ == "__main__":
exp_args = parse_commandline()
print("\tPerson name:", exp_args.name)
print("\tAge:", exp_args.age)
print("\tConvicted:", exp_args.ever_convicted)Now we don't have the problem we observed previously.
python3 04_casting_bool.py -n David -a 20 -ec False Person name: David
Age: 20
Convicted: False
python3 04_casting_bool.py -n David -a 20 -ec True Person name: David
Age: 20
Convicted: True
Sometimes it is interesting to create different types of arguments.
Some arguments can be required while others can be optional. Sometimes we want optional arguments to be mutually exclusive. For example, we might want a verbose argument to be mutually exclusive to a quiet argument.
If verbose we want the code to print a long description.
If quiet we want the code to simply return a number.
Notice that it does not make sense to want both. We can use a parser.parser.add_mutually_exclusive_group to precisely code this logic.
import argparse
import math
def build_argument_parser():
parser = argparse.ArgumentParser(description="Calculate Cylinder Volume")
parser.add_argument('-r', '--radius', type=float, metavar='', required=True, help='Radius of the cylinder')
parser.add_argument('-H', '--height', type=float, metavar='', required=True, help='height of the cylinder')
group = parser.add_mutually_exclusive_group()
group.add_argument('-q', '--quiet', action='store_true', help='print quiet, returns a single number')
group.add_argument('-v', '--verbose', action='store_true', help='print verbose, returns an explanation and numbers')
args = parser.parse_args()
return args
def cylinder_volume(radius, height ):
return math.pi * (radius**2) * height
if __name__ == "__main__":
args = build_argument_parser()
radius, height = args.radius, args.height
volume = cylinder_volume(args.radius, args.height)
if args.quiet:
print(volume)
elif args.verbose:
print("Cylinder volume of radius={} and height={} is {}".format(radius, height, volume))
else:
print("Cylinder volume is {}".format(volume))This example provides behavior changes depending on mutually exclusive arguments:
Standard version:
python 05_test_argparse_mutually_exclusive_group.py -r 10 -H 23Cylinder volume is 7225.663103256525
Verbose version:
python 05_test_argparse_mutually_exclusive_group.py -r 10 -H 23 -vCylinder volume of radius=10.0 and height=23.0 is 7225.663103256525
Quiet version:
python 05_test_argparse_mutually_exclusive_group.py -r 10 -H 23 -q7225.663103256525
Let us build a program to print tables (like csv files) in a pretty way in the terminal.
We can use pandas (a python library for manipulating tabular data) to do most of the work for us.
import pandas as pd
import argparse
def str2bool(v):
if isinstance(v, bool):
return v
if v.lower() in ('yes', 'true', 't', 'y', '1'):
return True
elif v.lower() in ('no', 'false', 'f', 'n', '0'):
return False
else:
raise argparse.ArgumentTypeError('Boolean value expected.')
def build_argument_parser():
parser = argparse.ArgumentParser(description="Pretty printer csv")
parser.add_argument('-f', '--file', type=str, metavar='', required=True, help='Input csv file to be printed')
parser.add_argument('-H', '--head', type=str2bool, metavar='', required=False, help='Wheather to print only the head of the csv or not (default=False)')
args = parser.parse_args()
return args
if __name__ == "__main__":
args = build_argument_parser()
df = pd.read_csv(args.file)
if args.head == True:
print("\n")
print(df.head())
print("\n")
else:
print("\n")
print(df)
print("\n")We can use the program
python show.py -f "mycsv.csv"