Reusing Code
Contents
Reusing Code#
One of the fundamental skills that every programmer must have is the ability to make their code reusable. Not just cut/paste reusable, but being able to be reused with in a variety of conditions without having to rework the code.
When you think about a simple task, like making a peanut butter and jelly sandwich, you can ask your friend to make that sandwich with just a few words, like “Would you make me a sandwich?”. This works because we are often taught how to do simple tasks as we grow up. But what if your friend has never made a sandwich before? Then it might take a few more instructions such as:
First, get the peanut butter from the pantry
Get the grape jelly from the refrigerator
Grab the white bread off the counter
Spread the PB on one slice of bread and the jelly on another
Fold the bread slices together with the PB and jelly touching
Even here we’ve shortcut a bunch of steps? For instance, “get the peanut butter from the pantry” means go to the pantry, open the door, finding the jar, grabbing the jar, walk back, put it on the counter…
Imagine now if your friend came over once a week for a PB&J and we had to explain how to do each of these steps every time. It wouldn’t take more than once or twice for us to figure out that it might be best to just write down the steps for our friend and give them the notecard of instructions to follow whenever they are hungry. That would be helpful, but what if our friend gets sick of grape jelly and would prefer instead strawberry jam? Or she would prefer her bread toasted first? In that case we’d need to create new notecards for each combination of outcomes.
This helps us to understand quite a bit about how computers work and why our program is important. Computers don’t have a memory, so we do have to tell the computer each time all the steps each time we want to perform a particular task! Just like our forgetful friend, the computer needs to be reminded of the steps to take every time. What’s more computers only do what we tell them to do (using programming languages). If we told the computer to get the grape jelly from the fridge and there wasn’t any grape jelly - our friend might automatically choose strawberry instead, the computer won’t make this choice for us unless it’s told that strawberry is suitable alternative to grape.
Computers can’t fill in the blanks, we have to be very precise. But we also don’t want to have to be extremely precise everytime we want to do a simple task, so we use abstractions in programming to help us with the mundane. One example, the print()
statement we saw earlier. There is a lot of stuff happening to make a couple of letters appear on the screen. Fortunately for us, someone else figured this out already and we can rely on their work.
Functions (in other languages called subroutines) are self-contained lines of code which accomplish a specific task. Functions can “take in” data, process it, and then “return” a result.
Defining Functions#
In Python we establish functions with the def
keyword (short for define) followed by the name of the function, a set of parenthesis, and a :
. To use the function, we just type it’s name and the parenthesis. (There are a few more options, but this is the simplest form.)
In the next cell, we have defined a function named say_hello_world
. Notice how it has the def
keyword, the name of the function, ()
and a :
? The body of the function, the code which is executed when we call (run) the function, consists of all the lines in function code block.
# This function will output the phrase, Hello World!
def say_hello_world():
print('Hello World!')
# Run the function
say_hello_world()
Now that we have defined the function, we can use it over and over again without having to define it again. Even better, if we decide to change it - we don’t need to update the code that is calling the function to change it.
# Use the say_hello_world function
say_hello_world()
You can try it here. Create a new code cell and type say_hello_world
into the new cell and see it prints out the same message.
Once you have done that, go back to the cell with the defintion of the function and change the text "Hello World!"
to "Hi World!"
. If you run that cell and then the next one you’ll see the new output.
Hiding the details#
Functions can replace several lines of code into a single statement when used, because they encapsulate the details. In this example, we just repeated the print()
function but what about something more complex like getting input from a user and then responding to the message. Could we shortcut all this into one function?
# Say hello to a particular user
def say_hello_user():
users_name = input("What's your name?")
print("Hello, ",users_name," it's nice to meet you!")
say_hello_user()
Functions can be very simple or much more complex. The best practice though is to make functions just big enough to complete a single task and no more. This could mean calling other functions and doing some other processing, but let’s not worry about complexity just yet.
Configurable functions#
Most often when defining functions, we want to complete a task with some specific parameters or configurable steps. For instance, the print
function wouldn’t be very helpful if we had to write a new function everytime we wanted to change the output! Instead, we can send in the characters we want printed to the screen using arguments. Functions define temporary variables to hold the value of these arguments, called parameters.
Starting with a simple example. Let’s rewrite our say_hello_user()
function, but this time, let’s supply the greeting and the users name outside of the function.
# Say hello to a particular user
def say_hello(name, greeting):
# The value of greeting and name are set by the caller of the function
print(greeting, name)
users_name = "Mr. Ed"
# We can pass parameters with a "fixed" value or as a variable
say_hello(users_name, "Hello")
say_hello(users_name, "Hi")
say_hello(users_name, "Ciao")
Getting something back#
Many functions are helpful just as they are, they do some work and then go away. Sometimes we want feedback from a function or to capture the result of doing some work in a function. In these cases, we use another Python keyword return
. The return
keyword says the function is complete and this bit of information is the result of the effort. As a caller of the function we can use the returned value just like we use variables, we can assign it to variable for safe-keeping, pass it along to another function or ignore it all together.
Let’s consider a simple example:
def add(a, b):
c = a+b
return c
Here we have a function named add
, we know it is a Python function because it has the word def
in front of the name. Our add
function “takes in” two values (parameters) named a
and b
, these two values become variables that exist only while the function is called. We call these function parameters. Finally, our function does something useful and sends back the result c
. So now, when we want to use this function, we just need to specify the name of the function, a value for each of the two parameters and then we can collect the result into another variable.
# Add the numbers represented by the parameters a and b
def add(a, b):
# The value for a and b are provided by the caller
c = a+b
# The value stored in the variable, c, is sent back to the caller
return c
# Call the `add` function, where the value `a` is 5, `b` is 4
# and the result of the fuction is stored in the new variable `return_value`
return_value = add(5,4)
print(return_value)
Now what we have is a function that will perform a set of actions, but we don’t have to know ahead of time what the values for a
and b
will be. We just know that they should be two numbers.
This is a very simple example, of coarse, in more complex functions the number of lines of code can be very long. As a matter of fact we often encapsulate many of the things we’ve already learned like making decisions and looping in our functions. We can even call other functions inside of our functions.
def multiply(x,y):
# Make sure that we have a variable to capture the ouput
# We set it to zero as a "default". So that if nothing happens in our function
# that at least we know that result of the execution will be a known value of 0
z = 0
for i in range(y):
z = add(z,y) # Calling our add function
return z
mult_result = multiply(5, 4)
print(mult_result)
Optional Parameters#
Sometimes we want the flexibility that providing parameters offers, but we also want to offer the caller a sensible default. For example, let’s go back to our greeting function.
# Say hello to a particular user
def say_hello(greeting, name):
# The value of greeting and name are set by the caller of the function
print(greeting, name)
# Say hi to Alice, Bob and Charlie
# We can pass parameters with a "fixed" value or as a variable
say_hello("Alice", "Hello")
say_hello("Bob", "Hello")
say_hello("Charlie", "Hello")
When we call the function, we want the ability to use another greeting (as we saw above), but if most of the time we just want to assume “Hello”, we can set this default in the function defintion.
# Greet a user by name
def greet_user(name, greeting="Hello"):
# The value of greeting and name are set by the caller of the function
# If the caller doesn't give us a value for `greeting`, then use "Hello"
print(greeting, name)
# Say hi to Alice, Bob and Charlie
# Say Hello to Alice
greet_user("Alice")
# Greet Bob with "Hi"
greet_user("Bob", greeting="Hi")
# Greet Charlie with "Yo!"
greet_user("Charlie", greeting="Yo!")
Why write functions?#
- Functions allow us to conceive the program as a series of steps where each substep is captured in its own block.
When a program seems too difficult, break down the substeps functions. And if necessary, break that sub-step down into another function, keep going until each substep is understandable and does exactly one task, then combine the steps together into bigger subtasks. - Functions allow us to reuse code.
This is essential, because it saves us from typos, ensures that we are doing calculations and tasks consistently whereever it is used and even allows others to use our code without understanding how the subtask is executed. - Functions encapsulate the variables keeping our "namespace" clean.
Since a, b, and c only contains values when the function is called, we can use the variable names a, b and c as parameters in other functions or in our main code without confusion. - Functions allow us to test our program in small pieces.
If we can execute a part of our program correctly by calling just one function, then we can continue to build our program knowing that the building blocks are solid.
Best Practices#
There are a few things that should be considered when creating functions in any language and a few things that are specfic to Python.
- Functions should perform exactly one task.
Functions should have a very specific and limited intention. - Documenting your function is a must.
You may not believe this now, but even the best programmers forget how their function is supposed to work or what it was supposed to do when the come back to use it a few days, months or years later. The convention in Python is to write documentation comments immediately after the function definition. - Function names, and their parameters should be as descriptive as possible.
While these examples above use parameter names like a and b (there is no reason to name them differently) the function names themselves immediately tell you the purpose and do exactly what you expect (add numbers, subtract numbers). - Function names ought to use all lowercase letters and use '_' to separate words.
If your function name is more than a single word, it is common practice (in Python, other languages differ) to separate the names with an `_` character. For instance - Function parameters should be descriptive
Functions can be self-documenting if the coder uses good names for the function name and also for the parameters.
def this_is_a_long_function_name(with_well_defined_parameters): return None
def power(x,y): ''' Raises x to the power of y and returns the result Parameters: ----------- x: int - the base number y: int - the exponent Returns: -------- int : result of performing the operation Example: -------- >>> power(2,3) 8 ''' return x ** y def add_subtract(x, y, add=True): ''' This function will either add or subtract the two values provided Parameters ---------- x : int the first value in the equation y : int the second value in equation Returns: -------- int : result of performing the operation Example: -------- >>> add_subtract(4,4) 8 >>> add_subtract(3,8, True) 11 >>> add_subtract(4,4, add=False) 0 >>> add_subtract(3,8, False) -5 '''
Conclusion#
You’ve learned quite a bit in this set of exercises and probably already knew some it. Next, put your skills to the test in the Exercises.