Functions (Good)

What to expect in this chapter

In this chapter, I will tie up some loose ends about functions like types of arguments and docstrings. I will also discuss exception handling so that you can better understand how to deal with errors. By the end of this chapter, you will know the difference between positional, keyword, and default arguments of functions. You can also write code that checks and handles potential problems.

1 Checks, balances, and contingencies

Users and programmers are not infallible, and you cannot think of everything that can go wrong. So, having checks, balances, and contingencies in your code is a good idea. So, let’s talk about pre-empting problems. This topic applies to more than just functions, but let’s start here.

There are two standard ways Python allows us to incorporate checks: assert and try-except.

1.1 assert

Python has a command called assert that can check a condition and halt execution if necessary. It also gives the option of printing a message.

The basic syntax is as follows:

assert condition-to-check, message

assert stops the flow if the condition fails. Here is an example.

assert x >= 0, "x is becoming negative!"

The program will run for as long as the condition is True. If it fails, then an AssertationError is raised, and the program stops running!

The following will run without a problem.

x = 10
assert x >= 0, "x is becoming negative!"

The following will throw an error and stop.

x = -1
assert x >= 0, "x is becoming negative!"

1.2 try-except

A technical name for things going wrong is exceptions. For example, division by zero will raise a ZeroDivisionError. An exception left unhandled will halt the flow of the programme. However, if you are a control freak, Python offers an (absurdly) simple ‘try-except’ structure to catch and handle these exceptions yourself.

The try-except syntax can also ensure that your programme can handle some situations beyond your control. For example, when I use Python to speak to the Canvas server, I use try-except to handle situations when the server does not respond.

Let me show you how to use the try-except flow control statement.

We can solicit a user response using the input() function. Let’s say we do this and ask for a number, as shown in the snippet below.

number=input("Give me a number and I will calculate its square.")
square=int(number)**2              # Convert English to number
print(f'The square of {number} is {square}!')

This will work fine if the typecasting int(number) makes sense. What if the input is not a number but something else like ‘hahaha’?

Let’s use the try-except to get around this problem.

try:
    number=input("Give me a number and I will calculate its square.")
    square=int(number)**2
    print(f'The square of {number} is {square}!')
except:
    print(f"Oh oh! I cannot square {number}!")

Notice how I have enclosed (and protected) that part of the code that we think can potentially lead to trouble in the try block. If something (anything) goes wrong, Python will ignore the error and run the code in the except block.

You can have more control over how the expetions are handled with a try-except block. However, we do not have to worry about that at this point.

1.3 A simple suggestion

When starting out with some code, it is always good for your code to signal to the outside world that it has finished certain milestones. A ‘soft’ way to do this is to include ‘print()’ statements here and there to let the outside world know what is happening in the innards of your program. Otherwise, you will stare at a blank cell, wondering what is happening.

2 Some loose ends

2.1 Positional, keyword and default arguments

In the past chapter, some of you may have noticed that I was (carelessly) switching between passing two styles of passing arguments to the function greeting(). I wrote greeting('Super Man') or greeting(name='Super Man'). We need to talk a bit more about this so that you are not bewildered when you see other people’s code.

There are three ‘ways’ to pass a value to an argument. I will call them positional, keyword or default. To make this clearer, consider the following function.

def side_by_side(a, b, c=42):
    return f'{a: 2d}|{b: 2d}|{c: 2d}'

Here are three ways I can use this function.

Positional

side_by_side(1, 2, 3)

Here, I am telling Python to assign 1, 2, 3 to a, b, c using the positional order of the arguments.

Keywords

side_by_side(c=3, b=1, a=2)

Here, I explicitly specify the keyword to assign the values to each of a, b, c. (No, the order does not matter)

Default

side_by_side(1, b=2)

Here, since c is optional, I can choose not to specify it (of course, provided I want c to be 1).

Below are some examples of how you can combine these three styles. However, one style (keyword followed by positional) confuses Python and won’t work.

side_by_side(1, 2)           # Two positional, 1 default
## ' 1| 2| 42'
side_by_side(1, 2, 3)        # Three positional
## ' 1| 2| 3'
side_by_side(a=1, b=2)       # Two keyword, 1 default
## ' 1| 2| 42'
side_by_side(c=3, b=1, a=2)  # Three keyword
## ' 2| 1| 3'
side_by_side(1, c=3, b=2)    # One positional, 2 keyword
## ' 1| 2| 3'
side_by_side(1, b=2)         # One positional, 1 keyword, 1 default
## ' 1| 2| 42'

Let me reiterate that the following will not work because Python cannot unambiguously determine the position of 1?

# Keywords cannot be followed 
# by positional arguments
side_by_side(a=2, 1)      # Won't work.                          

2.2 Docstrings

Python has a docstring feature that allows us to document what a function does inside the function. This documentation (i.e., the docstring) is displayed when we ask Python to show us the help info using help().

Here is a simple example.

def side_by_side(a, b, c=42):
    '''
    A test function to demonstrate how 
    positional, keyword and default arguments 
    work.
    '''
    return f'{a: 2d}|{b: 2d}|{c: 2d}'

A docstring needs to be sandwiched between a pair of ''' (or """) and can span multiple lines.

Let’s see if it works by asking for help.

help(side_by_side)
Help on function side_by_side in module __main__:

side_by_side(a, b, c=42)
    A test function to demonstrate how 
    positional, keyword and default arguments 
    work.

Docstrings can be used for writing multiline comments, but the practice is frowned upon by Puritans; so if you misuse it be ready for their ire!

2.3 Function are first-class citizens

Python functions are called first-class citizens because they have the same privileges as variables. This opens up useful possibilities for scientific programming because we can pass a function as an argument to another function!

Consider this:

def my_function(angle, trig_function):
        return trig_function(angle)

# Let's use the function
my_function(np.pi/2, np.sin)        
## 1.0
my_function(np.pi/2, np.cos)        
## 6.123233995736766e-17
my_function(np.pi/2, lambda x: np.cos(2*x))  
## -1.0

Note: When we pass a function as an argument, we do not include the parenthesis ().

2.4 More about unpacking

There is more to unpacking. For example, unpacking can make extracting information from lists and arrays a breeze. Here are some examples.

  1. x, y, z = [1, 2, 3]
    x, y, z
    (1, 2, 3)
  2. x, y, z = np.array([1, 2, 3])
    x, y, z
    (1, 2, 3)
  3. x, *y, z = np.array([1, 2, 3, 4, 5])
    x, y, z
    (1, [2, 3, 4], 5)
  4. x, *_, y = [1, 2, 3, 4, 5]
    x, y
    (1, 5)
Back to top