Python III: Functions and Debugging Strategies
Functions are an integral part of programming. They are self-contained pieces of code which provide instructions for a given task. Functions are called with certain input, execute the code specified, and then return specified value(s) from the calculations performed. The presence of a function in a program does not guarantee that it will run - functions must be explicitly called to run.
Functions should be an integral component of your scripts/program, allowing for modular, repeatable, and readable programs. Using functions dramatically lowers the chance of bugs occuring in your program and cleans up a lot of clutter.
If/when you perform a certain calculation multiple times throughout your program, or you find yourself copy/pasting code into many locations in a program, that code belongs in a function!
Basic Function Syntax
Functions are defined using a
def statement and their contents are indicated with whitespace (as with if, for, while):
The input argument (
argument) exists only with the scope of this function,
name_of_function. Similarly, variables created inside a function only exist inside the function. Arguments can be named arbitrarily, and they do not have to have any correspodence to variables in the main body of your code (see examples below).
For example, we can create a simple function that adds two numbers together. First, think about the actual task of addition itself: you are given two numbers, you add them together, and then you report the result.
Functions can also return multiple values, stored as a tuple. This means that the values returned are separated by commas and constitute an immutable container. This is good news - as tuples can’t be changed once created, the returned values are safe!
Positional vs. keyword arguments
In the previous examples, all functions took a pre-specified number of arguments in a particular order. These are called positional arguments - the order in which you provided the arguments actually matters for how those arguments are used inside the functions. Other types of arguments allowing for more flexibility, however, are possible!
If used in combination with positional arguments, all keyword arguments come at the end. Example,
A function can have as many keyword arguments as you want. When you call the function, you can specify these arguments in any order as long as they all come after all positional arguments. If you intend to use the default behavior of such an argument, no need to supply it! That’s why the defaults are there.
It is always (read: always) a good idea to incorporate docstrings into your functions. Docstrings are essentially comments placed inside three quotation-mark bounds (
""" words """) which explain the purpose, functionality, input arguments, and return values for your function. Docstrings are great because they explain to you and others looking at your code what exactly the function accomplishes, without the reader having to fully read and internalize all the code. Also, as a bonus, if you ever want to document your python code, there are awesome tools out there (like Sphinx) which will automatically create beautiful documentation from your python code using these docstrings.
The docstrings are also shown whenever call
help() on a given function.
Let’s rewrite the most recent
divide() function with docstrings included.
Dealing with poor function input
Generally, we write functions to accomplish a specific task, such as dividing two numbers as described above. However, there are ways for this to go wrong, for instance dividing by zero:
Or, perhaps the function is poorly documented and users don’t know that it requires arguments to be numbers (this is a trivial example, but goes to show that the wrong type of input can cause lots of problems).
We can catch specific errors with
if statements, for example:
if only captures one type of input problem, namely when the input arguments are not floats or integers. To catch any problem, we can use a construct called
try/except. Syntactically, these statements looks like
if/else statements. Their purpose is to allow Python to proceed even if errors happen:
If the code inside the
try block throws an error of any kind, then code within the
except block will be executed. Note that
try must have a corresponding
except statement! If you just want to skip over any errors without putting any explicit code in
except, use the
pass statement, for example:
Scope in Python
The concept of scope is very important in computer programming. For a given object, the scope of that object refers to where in the computer program the object’s name can be used to refer to its value. In other words, if I define a variable
my_variable, will Python always know what
my_variable is? Python is quite flexible in this regard (other languages are not!). The most important point to remember is that Python scope goes top-down: If a variable is defined at the top of a script, then throughout the rest of the script Python knows what that variable is. This is why functions are generally written at the top of a script, so that the functions can be used throughout the main body of a script.
Importantly, the scope of variables defined inside a function is the function itself - Python considers variables inside functions to exist only within the function. This means that, if you define a variable called
x inside a function but a variable called
x exists elsewhere in your script, Python will not get confused.
Like other language, Python has modules which can be imported for specific functionality. For example, last week we used the
random module to generate random DNA sequences. Modules are, actually, Python scripts which contain related functions.
This means we can write our own modules! Why would you do this? Let’s say you have several scripts which all perform similar tasks, and therefore require the same functions. One way to do this is simply to include your functions in every script. An alternative (and dare-I-say, better?) strategy is to create a stand-alone python script which contains only functions - this is a module! You can then import this module into the scripts which use these functions. This strategy will help ensure that you don’t accidentally introduce bugs from copy/pasting the function, and more importantly allows you to change the function only one time as opposed to individually in each script where it’s used (no matter how diligent you are, the latter strategy will introduce bugs!).
Interpreting Error Messages
Although Python error messages might seem initially cryptic, they actually contain lots of useful information! Follow this link to read about interpreting error messages for debugging your code.
Download this Python script (also available for download with today’s Cheatsheet, see home page), which contains poorly-organized Python code in need of modularization! Your task is to rearrange this script such that it contains functions, as described within the script, and is modular!