Python Command-Line Arguments: Part 3: getopt

Python Command-Line Arguments: Part 3: getopt

As we get into more details with python command-line arguments, we have to remember the generic household name in the command line family; getopt.

See, before we had the fancy argparse, we had this swiss army knife of a package. If you are a 'regular-rish' C programmer, I bet this rings a bell. As a notable difference between the two, there is definitely more code involved. There are a few more differences that will come to light as we explore the same program from before. Here goes. As a little challenge, we are going to leave the project section out just to see how far we can get without the extra nudge. Remember the square program?

import getopt
import sys
from math import sqrt

"""
get the square but get the square root in case the argument 'root' is provided
"""


def usage():
    """
    Show help for the CLI program
    """
    print("python advanced_square.py --number <your_number> \n OR\n")
    print("python advanced_square.py -n <your_number>\n")
    print("To get the square root: python advanced_square.py -n <your_number> -r")
    print("Example: get the square\n\tpython advanced_square.py -n 5")



def main():

    try:
        option, arguments = getopt.getopt(sys.argv[1:],"hn:r",["help","number=","root"])
    except getopt.GetoptError as error:
        print(error)
        sys.exit()

    # initialize variables for for loop
    number = None
    root_number = False

    for opt, variable in option:
        if opt in ("-h", "--help"):
            usage()
        elif opt in ("-n", "--number"):
            number = int(variable)
        elif opt in ('-r','--root'):
            root_number = True
        else:
            usage()
            sys.exit()

    if root_number:
        print(f"The square root of {number} = {sqrt(number)}")
    else:
        print(f"The square of {number} = {number* number} ")



if __name__ == '__main__':
    main()

What this program does is simple. Get the square of a number parsed as a command line or get it's root if the --root or -r is parsed.

To run it.

# get the square of 5
python advanced_square.py -n 5

# get the square root of 5
python advanced_square.py -n 5 --root

Let's break this down. Unlike grandson argparse which just knows how to display helpful messages, in getopt we do not get this right out of the box. So running python advanced_square.py will simply give a user an error screen like so

Traceback (most recent call last):
  File "advanced_square.py", line 48, in <module>
    main()
  File "advanced_square.py", line 43, in main
    print(f"The square of {number} = {number* number} ")
TypeError: unsupported operand type(s) for *: 'NoneType' and 'NoneType'

we have to specify how we want this run. That's where our custom function usage comes in. Telling the user, 'Hey, know what? You messed up'

Take a look at this line here:

option, arguments = getopt.getopt(sys.argv[1:],"hn:r",["help","number=","root"])

What getopt.getopt() does is accept three arguments,

#Accept a list of command-line arguments excluding the program #name itself.
sys.argv[1:]

Accept short options. The reason why in your Unix-like terminal you can say ls -a or ls --all to list contents of a directory including hidden files. -a would be the short option in this instance

"hn:r"

Note how we show short options. Our program, advanced_square.py accepts three short options, namely, h for help, n for the number whose square or square root we want, and r to specify whether it is indeed the number's root we need.

See this n:? That little colon after the letter n specifies that after we write n, we need the actual number parsed. Hence -n 5 or -n 29 and so on. The long option alternative, as you might have noticed, would be number=. We add an equal sight to it to show that this option needs an argument to follow it, unlike its counterpart root and help

We follow it up with r to specify that the user can just write -n 5 -r which would mean get me the root of 5. The long alternatives to this program would be :

python advanced_square.py --number 5 --root

The order does not really matter. Whether --number comes after --root or not is up to you. getopt will know what to do. Isn't that cool?

Just note, however, that if you specify n to be capable of receiving a number, then that is what must follow it. So you cannot do this

# wrong move
python advanced_square.py --number  --root 5

As a whole;

getopt.getopt(sys.argv[1:],"hn:r",["help","number=","root"])

The return of the above we get two items. The first, which we name 'options' looks like this [('-n', '5')] while the second, named 'arguments' is simply this [], an empty list! That's odd. One might say.

The arguments variable holds what extra arguments have been parsed to the program. So if a user does:

python advanced_square.py -n 7  9

options will look like:[('-n', '5')] while arguments will look like ['9']. It would hold all those weird extras you might pass in. So if we randomly decide to use:

python advanced_square.py -n 7  -h

options would look like this [('-n', '7'), ('-h', '')], a list of tuples. See some light at the end of the tunnel?

Let's move to the next line.

getopt.GetoptError as error:

Notice how we get the error. In this way, anything that is not in the required command-line argument specification is caught as an error. For instance, you use python advanced_square.py --animal 5. In such a case, we want to display the error message and gracefully exit the program. In short, using the phrase animal is nowhere defined in our program!

Because our main focus is on the arguments the program actually needs, we abandon use on the arguments(extras) variable and loop via the list of the options. We are saying, if ('-n', '7'), in this case, n is present and has value, take it and do '1 2 3' else, if ('h', '') which is present is there, call this function - help me!.

We convert the value of the parsed item, our '5' to an integer as getopt assumes everything coming in is a string. So we change the input to an int and assign it to a variable called 'number' number = int(variable)

Getting the number and the value of root as either True or False, we can safely get our square and root for use through the program.

This walk through's code, as usual, is at TheGreenCodes. Till next time though, adios!