Scripting

OK, so we’ve spent quite a long time in Python shells. But we can quit. With IPython’s history we can only get back our work line-by-line. When we want to persist what we’ve done, we write code to a file and then run the file.

Let’s create a file called pizzaiolo.py, and write a little python in it.

import time

def make_pizza(*toppings):
    """Make a delicious pizza from the toppings."""
    print('Making pizza...')
    for topping in toppings:
        print('Adding {0}'.format(topping))
        time.sleep(1)
    print('Done!')
    return 'Pizza with toppings: {0}'.format(toppings)

pizza = make_pizza('cheese', 'olives')

To run it, we can run the python or ipython programs in our shell, passing the filename as an argument.

$ python pizzaiolo.py
Making pizza...
Adding cheese
Done!
Adding olives
Done!
$ ipython pizzaiolo.py
Making pizza...
Adding cheese
Done!
Adding olives
Done!

We can enter an interactive shell after the script has run by including the -i flag, in which we’ll have access to anything that was defined by the script.

$ python -i pizzaiolo.py
Making pizza...
Adding cheese
Done!
Adding olives
Done!
>>> time
<module 'time' from '/usr/lib/python2.7/lib-dynload/time.so'>
>>> pizza
"Pizza with toppings: ('cheese', 'olives')"
>>> exit()

$ ipython -i pizzaiolo.py
Making pizza...
Adding cheese
Done!
Adding olives
Done!

In [1]: pizza
"Pizza with toppings: ('cheese', 'olives')"

In [2]: exit()

$

One of the most interesting things you can do in your own scripts is accept arguments. Wouldn’t it be great if we could decide what toppings our pizza has from the command line?

$ python pizzaiolo.py cheese broccoli
Making pizza...
Adding cheese
Done!
Adding olives
Done!

Of course, nothing’s changed because our script doesn’t know how to handle such arguments. To add this, we use for example the sys module, which makes the command line arguments available as the argv property. We can modify our script to print this out, to get a feeling for what’s going on. We’ll comment out our method call whilst we’re just playing around.

import sys
import time

def make_pizza(*toppings):
    """Make a delicious pizza from the toppings."""
    print('Making pizza...')
    for topping in toppings:
        print('Adding {0}'.format(topping))
        time.sleep(1)
    print('Done!')
    return 'Pizza with toppings: {0}'.format(toppings)

print('sys.argv:', sys.argv)
# pizza = make_pizza('cheese', 'olives')

Then run it:

$ python pizzaiolo.py
sys.argv: ['pizzaiolo.py']
$ python pizzaiolo.py cheese broccoli
sys.argv: ['pizzaiolo.py', 'cheese', 'broccoli']
$ python pizzaiolo.py cheese broccoli --help --verbose
sys.argv: ['pizzaiolo.py', 'cheese', 'broccoli', '--help', '--verbose']

Awesome! sys.argv is just a list with one value per argument (arguments on the command line are separate by spaces). The first value is always the name of the script that we run.

So, let’s use the command line arguments in our script.

# print('sys.argv:', sys.argv)
toppings = sys.argv[1:]
pizza = make_pizza(*toppings)

And then run it:

$ python pizzaiolo.py cheese broccoli
Making pizza...
Adding cheese
Done!
Adding broccoli
Done!

Super cool. Now we could decide to add some flags that modify the behaviour of our program. We might like the --help flag to print a help message and exit, without actually making pizza, and a --verbose flag to enable printing more information.

arguments = sys.argv[1:]

if '--help' in arguments:
    print('Make a pizza.')
    print('Usage: pizzaiolo.py topping1 topping2 ...')
    sys.exit()

if '--verbose' in arguments:
    verbose = True
    # We don't want the flag passed as a topping!
    arguments.remove('--verbose')
else:
    verbose = False

if verbose:
    print('About to call make_pizza')
pizza = make_pizza(*arguments)
if verbose:
    print('Finished')

Let’s try running it.

$ python pizzaiolo.py cheese broccoli
Making pizza...
Adding cheese
Done!
Adding broccoli
Done!
$ python pizzaiolo.py cheese broccoli --help
Make a pizza.
Usage: pizzaiolo.py topping1 topping2 ...
$ python pizzaiolo.py cheese broccoli --verbose
About to call make_pizza
Making pizza...
Adding cheese
Done!
Adding broccoli
Done!
Finished

Great, everything seems to work.

argparse

The argparse module comes as part of the Python standard library, and allows us to define what arguments our scripts accept much more easily than what we’ve shown. Under the hood, it just inspects sys.argv in exactly the same way as we’ve done, but it takes care of things like validation for us.

import argparse
import sys
import time


def make_pizza(*toppings):
    """Make a delicious pizza from the toppings."""
    print('Making pizza...')
    for topping in toppings:
        print('Adding {0}'.format(topping))
        time.sleep(1)
    print('Done!')
    return 'Pizza with toppings: {0}'.format(toppings)


parser = argparse.ArgumentParser(description='Make a pizza')
parser.add_argument('toppings', nargs='+',
                    help='Toppings to put on the pizza.')
parser.add_argument('--verbose', '-v', action='store_true',
                    help='Print more information whilst making.')
arguments = parser.parse_args()

if arguments.verbose:
    print('About to call make_pizza')
make_pizza(*arguments.toppings)
if arguments.verbose:
    print ('Finished')

We say that we want an argument, that we will refer to as toppings in the code, that can be specified multiple times nargs='+', and a flag called --verbose. We ask that that flag can also be specified using the -v shorthand.

Let’s start by asking for help again.

$ python pizzaiolo.py --help
usage: simple.py [-h] [--verbose] toppings [toppings ...]

Make a pizza

positional arguments:
  toppings       Toppings to put on the pizza.

optional arguments:
  -h, --help     show this help message and exit
  --verbose, -v  Print more information whilst making.

Woah, nice! We didn’t even tell argparse to have a --help flag, but we have one automatically. (argparse will also add -h as an alias for --help.)

Let’s now prepare the traditional Pizza Margherita.

$ python pizzaiolo.py 'tomato sauce' 'buffalo mozzarella' --verbose
About to call make_pizza
Making pizza...
Adding tomato sauce
Done!
Adding buffalo mozzarella
Done!
Finished

The same result is obtained, but more cleanly expressed and with fewer lines of code!