Template Training

Revision of Functions in Scripts

Overview

  • Teaching: 15 min
  • Exercises: 15 min

Questions

  • How can we put our new assertions into a scipt that we can run or import?

Objectives

  • Revise how to define a function in a script.
  • Recall how to use docstrings to auto-document your functions.
  • Know how to call the function within a script.
  • Know how to make a script importable.

In the previous lesson we wrote our function with its assertion in notebooks. As we have discussed previously, it is much more useful to write these as a script which we can then call, or in the case of libraries import. In this lesson we will write our mean function as a script and revise how to display documentation and call the function. We will then expore how to include our own libraries within other scripts revise why it is good practice to include the main part of the script in its own function.

If you have not already created a directory my_testing in your intro-testing directory, do so and cd into it. Then create a new file called mean.py. You are free to use the editor of your choice, e.g. nano or others on linux, or use the notebooks text editor.

Note that this will not be available on remote linux machines so you should be confident to use editors like nano if required.

Writing Scripts

Edit your new file mean.py so that it reads:

def mean(sample):
    '''
    Takes a list of numbers, sample

    and returns the mean.
    '''

    assert len(sample) != 0, "Unable to take the mean of an empty list"
    for value in sample:
        assert isinstance(value,int) or isinstance(value,float), "Value in list is not a number."
    sample_mean = sum(sample) / len(sample)
    return sample_mean

numbers = [1, 2, 3, 4, 5]

print( mean(numbers) )

no_numbers = []

print( mean(no_numbers) )

word_and_numbers = [1, 2, 3, 4, "apple"]

print( mean(word_and_numbers) )

Finally we can execute our script, you can do this form the terminal but we will run from within notebooks as we did before. in your library, navigate to the folder containing your mean.py file and luanch a new notebook:

%%run mean.py

You should see an output that looks like:

Traceback (most recent call last):
  File "./mean.py", line 22, in <module>
    mean(no_numbers)
  File "./mean.py", line 10, in mean
    assert len(sample) != 0, "Unable to take the mean of an empty list"
AssertionError: Unable to take the mean of an empty list

Our scripts executes each of line of code in turn and stops when it reaches the first AssertionError, even though this is how we intend the program to execture, Python doesn't know this and it means that it does not yet run the third of our tests. We will learn how we can run multiple tests, and encapsulate them in successful tests even if they intentionally raise errors.

Now we want to explore our script in a little more detail. Remember in our previous lesson we imported libraries into our interactive session. Let's see what happens when we do this with our new script. Let's try to import mean.py as a library:

import mean
3.0
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-1-330bb4995352> in <module>
----> 1 import mean

/home/rjg20/training/arc-training/intro-testing/mean.py in <module>
     20 no_numbers = []
     21
---> 22 print( mean(no_numbers) )
     23
     24 word_and_numbers = [1, 2, 3, 4, "apple"]

/home/rjg20/training/arc-training/intro-testing/mean.py in mean(sample)
      8     '''
      9
---> 10     assert len(sample) != 0, "Unable to take the mean of an empty list"
     11     for value in sample:
     12         assert isinstance(value,int) or isinstance(value,float), "Value in list is not a number."

AssertionError: Unable to take the mean of an empty list

The Scope of a Script

What we would like to do is change the behaviour of our program so that the functions when it is imported, but the remainder of the scripts only executes when we run it from the command line. This is called limiting the scope of the script, open your script and edit it so that it reads:

def mean(sample):
    '''
    Takes a list of numbers, sample

    and returns the mean.
    '''

    assert len(sample) != 0, "Unable to take the mean of an empty list"
    for value in sample:
        assert isinstance(value,int) or isinstance(value,float), "Value in list is not a number."
    sample_mean = sum(sample) / len(sample)
    return sample_mean

def main():
    numbers = [1, 2, 3, 4, 5]

    print( mean(numbers) )

    no_numbers = []

    print( mean(no_numbers) )

    word_and_numbers = [1, 2, 3, 4, "apple"]

    print( mean(word_and_numbers) )

if __name__ == '__main__':
    main()

Now run this as a script and also try importing it in ipython. name determines the scope of a script, if it is run as a script it is set to ‘main’, but not when it is imported, allowing the behaviour to be different in the two cases. You can find out more here.

Update the documentation

We have some documentation in the mean function but this could be improved to give more details on the functions behaviour, and there is no documentation for our main function.

Add appropriate documentation and verify that it works correctly.

Solution

What is missing?

You are putting your code into scripts, preserving the code and enabling it’s re-use. You are developing tests allowing you to help demonstrate that the code is working as in tended. And you have documented your code so that you and other users can make use of the code in the future, But what have we forgotten?

Solution

Key Points

  • Writing functions in scripts makes reusable
  • Documenting your functions is essential to make them re-usable
  • All code in a script is executed by default.
  • Specify a ‘main’ function which is only executed when run as a script.
  • The ‘script’ can then be imported as any other python library.