# Functions

We've been using functions already, such as print() or len(), but we've not really discussed what they are.

In this chapter you'll learn more about them and also how to create your own!

# What are functions?

Generally speaking, there are two types of functions:

  • Ones that perform an action, such as print().
  • Ones that receive inputs and return outputs, such as len().

Of course some functions fit into both categories.

We will start by looking at functions that perform an action.

# Why are they useful?

Functions are extremely useful because they let you define a block of code once and use then use it in many places.

For example, let's say the print() function did not exist. In every one of the small programs you write, you would have to figure out how to do what the print() function does and write all the code each time.

However, because we have functions, we can just use the print() function that has been defined previously and stored within Python!

# How do you define a function?

In addition to the built in functions like print(), we can also write our own functions. We'll look at some examples, but first let's learn how you actually define one.

Here's how you define a function:

def say_hello():
    print("Hello!")
1
2

There are a few new parts, and some familiar parts!

  • First comes the def keyword, short for define. This tells Python we're about to define the behaviour of a function.
  • The next thing is the function name. For example, print, len, or, in our case, say_hello.
  • Then comes a set of brackets. You always need this for function definitions.
  • Then we have ourselves a colon.
  • Finally, the indented body which will run whenever we execute this function.

Try that out in the editor below!

Once you've tried it, you'll realise it... doesn't actually do anything. What?

It's because defining a function does not execute the function.

# How do you execute a function?

In order to execute a function, we use the function's name and a pair of parentheses like below:

def say_hello():
    print("Hello!")

say_hello()  # Execute the function
1
2
3
4

Make sure to try it out in the editor above.

Once we've defined the function, Python knows that it exists. We can then use it by writing a new unindented line of code that just executes the function.

To execute a function you don't use def, but it's important to remember to use parentheses. If you try to execute the function without any parentheses, the function won't run.

TIP

Other names for executing a function are calling the function or running the function. I will use these interchangeably, but calling a function is most commonly used.

# So what happens when you call a function?

Remember, there's no magic in programming! The computer simply executes your code line by line.

Functions work around this a little bit. The code stops executing sequentially (line by line, from top to bottom). Instead, it can begin jumping around.

Let's look at an example:

def user_age_in_seconds():
    user_age = int(input("Enter your age: "))
    age_seconds = user_age * 365 * 24 * 60 * 60
    print(f"Your age in seconds is {age_seconds}.")

print("Welcome to the age in seconds program!")
user_age_in_seconds()

print("Goodbye!")
1
2
3
4
5
6
7
8
9

Python still runs line by line, but when Python encounters the def keyword it knows we've arrived at a function definition. Python therefore defines the user_age_in_seconds function; however, Python does not execute the function, as we know.

Instead, when Python encounters a def statement, it remembers the function name we've defined but it skips the entire function body.

Python jumps to line 6, and runs the print() function.

Then we move onto line 7, and where it calls the user_age_in_seconds() function we defined on line 1. Python therefore jumps to line 2: the beginning of the function body. On line 2, we ask the user for input, turn that input into a string, and create a variable for it. The rest of the function body is then executed as normal.

When the function has finished executing, Python jumps back to where it was before the function began executing. That's line 7.

At this point, all the variables defined inside the user_age_in_seconds function are no longer accessible. For all intents and purposes, they are deleted. The user_age and age_seconds variables no longer exist.

We finally move onto line 9, and run the print() function again.

The program then ends.

The key takeaway is that:

  • Python jumps into a function when we call it, and back to where it was when the function ends.
  • What happens in Vegas stays in Vegas: any variables defined in a function are deleted after the function ends.
  • Not mentioned, but the function can use variables defined outside of it. That's totally fine!

# Common pitfalls

# Reusing names

It's generally a bad idea to reuse the names of functions in your code. Don't define a new function with the same name as one that already exists. Doing so will make Python forget about the old function.

For example, this would most likely not do what you want:

def print():
    print("Hello, world!")

print()
print("Bye, world!")  # Error
1
2
3
4
5

The problem with the above code is that print is actually the function we've defined, and not the print we've been using throughout this course.

print("Bye, world!") would give you an error because we've not defined our print function to be able to accept values inside the brackets (we'll learn about it in the next chapter!).

Of course this "reusing names" suggestion applies for both system functions (like print) and also for functions you've defined.

Doing this is normally a bad idea:

def say_hello():
    print("Hello!")

say_hello()  # Hello!

def say_hello():
    print("Hello, world!")

say_hello()  # Hello, world!
1
2
3
4
5
6
7
8
9

Your programs will quickly get very confusing if you reuse function names. My advice: don't do it!

# Reusing names (unwittingly)

Reusing names is generally bad, but it's even worse when we reuse names without realising.

For example, here's an example of a beginner Python programer reusing a variable name without realising:

friends = ["Rolf", "Bob"]

def add_friend():
    friend_name = input("Enter your friend name: ")
    friends = friends + [friend_name]  # Another way of adding to a list!

add_friend()
print(friends)  # Always ['Rolf', 'Bob']
1
2
3
4
5
6
7
8

Remember we mentioned variables created inside a function disappear when you reach the end?

The friends variable defined inside the add_friend function is actually not the same variable as the friends variable defined at the top of program.

It's a variable with the same name that has been re-declared inside the function, and which disappears when you reach the end of the function.

Instead what our programmer probably wanted to do is:





 




friends = ["Rolf", "Bob"]

def add_friend():
    friend_name = input("Enter your friend name: ")
    friends.append(friend_name)

add_friend()
print(friends)  # Could be ['Rolf', 'Bob', 'Anne']
1
2
3
4
5
6
7
8

That's because we don't have a friends = line inside our function, so no new variable is created. Python is smart enough to use the variable defined outside the function.

He could also keep his existing code by using the global keyword:

friends = ["Rolf", "Bob"]

def add_friend():
    friend_name = input("Enter your friend name: ")
    global friends
    friends = friends + [friend_name]  # Another way of adding to a list!

add_friend()
print(friends)  # Could be ['Rolf', 'Bob', 'Anne']
1
2
3
4
5
6
7
8
9

What global friends does is tell Python to declare a variable called friends and make it equal to the global friends variable.

The following line then changes the value of the variable instead of creating a new one.

# Using a function before defining it

In Python we cannot use anything before we've defined it. This applies for variables, functions, and other constructs.

This would give you an error (a NameError in fact!):

say_hello()

def say_hello():
    print("Hello!")
1
2
3
4

This is what you'd see:

NameError: name 'say_hello' is not defined
1

However, in the following example, everything works just fine.

def add_friend():
    friends.append("Rolf")

friends = []
add_friend()

print(friends)  # [Rolf]
1
2
3
4
5
6
7

So, why does this example work? Well, technically friends is still defined before it gets used inside add_friend. Remember that when we define a function, Python essentially skips over the function body. It only runs the code inside when we call the function, and add_friend gets called after the creation of our variable friends.

# Defining an empty function

The function body (the indented block) must exist.

This is actually true for functions and for everything else—if statements, loops, etc.

Don't do this:

def say_hello():

say_hello()
1
2
3

It'll give you an error because Python expects an indented block after a colon.

However, you can define empty blocks (e.g. in a function, if statement, or any other block) by using the pass keyword.

This is OK:

def say_hello():
    pass  # The indented block

say_hello()  # Does nothing
1
2
3
4

Of course, nothing happens because the function is what's called a "noop" function. That stands for "no operation".

TIP

As I'm developing programs, I often think about what functions, if statements, and loops I'll need and define them with empty bodies using pass.

Then I go back and "fill them in".

Doing so allows me to start thinking about the program's structure before going into detail.

# Using too few functions

Often when we're starting programming we like to keep code sequential because it's much easier to understand, and because we're not sure how many functions we should be using.

The first secret is: if you do the same thing twice, make it a function.

For example, let's say you have this program:

books = []

print("This program is about your favourite books.")

user_input = input("Enter your next top book: ")
books.append(user_input)

user_input = input("Enter your next top book: ")
books.append(user_input)

print(f"Your favourite book is {books[0]}.")
print(f"Your second favourite book is {books[1]}.")
1
2
3
4
5
6
7
8
9
10
11
12

This can be re-written using functions like so:

books = []

def add_new_book():
    user_input = input("Enter your next top book: ")
    books.append(user_input)

print("This program is about your favourite books.")

add_new_book()
add_new_book()

print(f"Your favourite book is {books[0]}.")
print(f"Your second favourite book is {books[1]}.")
1
2
3
4
5
6
7
8
9
10
11
12
13

It could also be written without functions, using loops:

books = []

print("This program is about your favourite books.")

for _ in range(2):
    user_input = input("Enter your next top book: ")
    books.append(user_input)

print(f"Your favourite book is {books[0]}.")
print(f"Your second favourite book is {books[1]}.")
1
2
3
4
5
6
7
8
9
10

TIP

range(2) gives you something Python interprets as a list: [0, 1]. That's why that for loop works.

The variable name, _, is commonly used when we need to create a variable (the for loop expects one) but we don't actually want to use it.

For maximum readability though, I would actually use both. That's because of secret no. 2:

books = []

def add_new_book():
    user_input = input("Enter your next top book: ")
    books.append(user_input)

print("This program is about your favourite books.")

for _ in range(2):
    add_new_book()

print(f"Your favourite book is {books[0]}.")
print(f"Your second favourite book is {books[1]}.")
1
2
3
4
5
6
7
8
9
10
11
12
13

Secret no. 2 is: if you can come up with a simple name for the block of code you want to run, make it a function.

I think add_new_book is quite a good name for that function, so I would almost always define a function for that code.

That's because when I'm looking through the program, it's easier to understand what this does:

for _ in range(2):
    add_new_book()
1
2

Than to understand this:

for _ in range(2):
    user_input = input("Enter your next top book: ")
    books.append(user_input)
1
2
3

# Using too many functions

It's possible to go the other way, and use too many functions.

Follow secrets no. 1 and no. 2 to avoid this pitfall.

Here's something I've seen before:

books = []

def about():
    print("This program is about your favourite books.")


def add_new_book():
    user_input = input("Enter your next top book: ")
    books.append(user_input)


def favourite_book():
    print(f"Your favourite book is {books[0]}.")


def second_book():
    print(f"Your second favourite book is {books[1]}.")


about()

add_new_book()
add_new_book()

favourite_book()
second_book()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

I can understand why the student thought it was a good idea, but in my opinion it's taking a bit too far. There's no real need to define all the one-liner functions that just print things out.

Although it can help readability when you get to the end of the file, in order to thoroughly read the program it takes longer and requires more jumping around.

It's always up to the programmer how many or how few functions to define. If you think something should be a function, make it one!

# Worrying about number of lines

When beginners start learning about functions, they quickly realise something:

It normally takes more lines of code to develop something that uses functions rather than something that is purely sequential.

This is often true with small programs. For example, before introducing functions in the examples above we were at 12 lines.

When we added functions and loops, we actually added a line of code. It's only one, but this is more exaggerated in other programs.

Many students believe loops and functions are there to decrease the number of lines in their programs.

However, and this is important, the number of lines of code in your program is (almost) completely irrelevant.

Your main worry should be readability and ease of development. That's what functions and loops are for!

Last updated: 5/5/2022, 3:50:22 PM