Pretty much everything that happens in a computer program can be summed up with four key principles. You’ll read about these very soon in the first section of this Chapter. You’ll feel familiar with three of them based on what you’ve read in the first two chapters. Now it’s time to cover the last of the key fundamental topics in coding, which is defining functions.
But before you learn about defining functions you can find out about the four categories of tasks in a computer program.
Store | Repeat | Decide | Reuse
You can think of everything a computer program does to fall into one of four categories:
- Store
- Repeat
- Decide
- Reuse
Let me start by saying that this is an oversimplification. Programming can get more complex than this. But it’s a reasonable simplification to think about as you learn.
Store
Computer programs rely on data. Data could be anything from a single number or name to a large, complex data set. In the Angry Goblin game, the data you needed included:
- the name of the player
- the number of doors in the game
- the position of the goblin
- the player’s guess
- the flag to indicate whether the game is still running, which you use in the
while
statement
Computer programs store information in variables. You’ve seen two ways of assigning data to a variable. The most basic method is by using =
, which is the assignment operator. You’ve also seen there’s an assignment of data embedded within the for
statement when you write a for
loop.
It may be tempting to think that storing information is essential but not the most challenging thing in coding. When we talk about more data types in the future, you’ll see that storing data is not always the most straightforward task. It can be the area you’ll need to spend the most time thinking about and planning before you start coding.
Repeat
One of the primary manifestations of the DRY principle is when you want to repeat some actions several times in a row. You don’t want to repeat things yourself, so you get the computer to repeat them instead. There will be many things that require the repetition of a block of code in computer programs you’ll write. You’ve learned about the for
and while
loops which are the two ways of creating loops in Python.
There are other constructs in Python coding that could fall under the repeat category. However, the two loops are the main ones and the only ones you’ll need to be familiar with for the time being.
Decide
Another common action that a computer program does is decide what course of action it needs to take. These are the decisions that only the program can make because they depend on other things happening as the program runs. The if
statement is the Python construct that deals with computer programs making decisions.
However, you’ve used another method that relies on the program making a decision. The while
statement needs to decide whether to execute the code inside the loop or skip it and let the program move on.
Reuse
Often, you’ll need to perform the same or similar actions in different parts of your computer program or different programs. You’ll know by now that copying and pasting the code wherever you need it is WET. If you decide to make a change to this bit of code or fix an error, you’d have to search for every place you’ve pasted this code to make the change everywhere.
The way to reuse code wherever you need it is to use functions. You’ve used several functions already. Whenever you need to display some information to the user, you call the print()
function, and whenever you want to get a random number, you can call the randint()
function from the random
module.
But what if you’ve written some code that performs a specific action that’s useful in your code, and you want to reuse that code whenever you need to? The solution is to define your own functions.
The Finding Names Project
In Chapter 1, you worked on a project through which you learned about several fundamental topics in coding. It’s time for another project to lead you into defining functions. You won’t be coding a game this time. Instead, you’ll start looking at how you can work with data and how to analyse, filter and manipulate data.
Project Description: You have a very long list of names. You need to write code that will help prospective parents choose a name for their child by filtering names based on some simple requirements.
Your first task will be to write code that filters the names based on the first letter in the name. For example, you’ll need to display all the names that start with the letter P.
First, Solve The Problem. Then, Write The Code
A well-known quote in programming is the following one:
First, solve the problem. Then, write the code.
John Johnson
This statement may seem obvious, but it highlights two points that are essential when developing a program:
- The distinction between subject-specific knowledge and programming-specific knowledge
- The importance of planning your programming task before starting to write code
What do I mean by subject-specific and programming-specific knowledge? Consider a computer program that simulates the propagation of coherent light through a diffraction grating. I happen to be a physicist with a PhD in Optics, so the physics of how light travels and interacts with objects is a topic I know very well. But not many people will be that well-versed in the subject. I have the subject-specific knowledge required to solve this problem.
However, my knowledge of physics is not sufficient to write the program to create the simulation. Programming-specific knowledge is required to write the code. A physicist who can’t code will not be able to write the simulation, no matter how good their physics knowledge is. And not even the best computer programmer in the world will be able to complete the simulation unless they’re very familiar with the science.
Every programming task has a subject-specific component and a programming-specific component. Even the Angry Goblin game you coded in Chapter 1 required you to understand the game’s rules. Granted, they weren’t too difficult, which means you didn’t have to spend any time thinking about the subject-specific knowledge, but that knowledge was required nonetheless.
The subject-specific part may be very simple or complex, depending on the problem you are trying to solve. If you want to look through a list of numbers to find all those greater than 10, then simple arithmetic is the only subject-specific knowledge that’s required.
Often, to solve your subject-specific problem, you may want to ask yourself the following question: How would I perform this task by hand, using pen and paper, assuming time wasn’t an issue?
Solving the Finding Names problem
If you want to find all the names starting with the letter P from a very long list of names, how would you do it using pen and paper? Imagine you were transported to the 1940s, before the computer era. How would you perform the task? What are the steps you’d need to take?
The key here is to break this task into distinct steps, including the obvious ones. Steps which may seem obvious to us human beings are not obvious to a computer.
Before you read further, get a sheet of paper and a pen or pencil, and try to write down the steps you would need to take. Include as much detail as possible.
Ready?
You can now read on. Here is a set of steps that can describe this task:
- Look at the first name in the list
- Look at the first letter of that name
- If the letter is the one you want, write the name down in a new list
- If the letter is not the desired one, do nothing
- Look at the second name in the list and then repeat steps 2 to 4
- Indeed, repeat steps 2 to 4 for all the names in the list
Once you have these steps, you’ve solved the subject-specific problem. In this case, the solution wasn’t too complicated, although you have to be careful not to skip any steps that humans might take for granted. Note that the steps listed are simply ideas, written in plain English. You don’t need to be thinking about Python or programming at this stage.
The next step is to translate the above ideas from English—or whatever language you may be thinking in—into Python—or whatever language you may be coding in. Every time you write a computer program, you’ll have to go through this process. Sometimes the task will be easy, and you can complete the planning stage in your head. Other times you’ll need to spend some time making sure you fully understand the subject-specific knowledge and finding a solution to the problem before you start coding.
Translating The Ideas From English To Python
Now that you’ve used your subject-specific knowledge to write the steps you need to solve this problem, you can start applying your programming-specific knowledge.
Your starting point for this project will be a list
containing names as str
. Later in this book you’ll learn how to read in data from external files, but for now this is a good starting point. Here is the list of names you’ll use:
list_of_names = ['Amelia', 'Olivia', 'Emily', 'Alexey', 'Poppy', 'Ava', 'Isabella', 'Jessica', 'Marcus', 'Lily', 'Sophie', 'Grace', 'Vsevolod', 'Sophia', 'Mia', 'Evie', 'Ruby', 'Celim', 'Sumir', 'Ella', 'Scarlett', 'Ruben', 'Isabelle', 'Chloe', 'Cherlin', 'Sienna', 'Masha', 'Freya', 'Phoebe', 'Charlotte', 'Daisy', 'Alice', 'Florence', 'Eva', 'Sofia', 'Millie', 'Lucy', 'Evelyn', 'Elsie', 'Rosie', 'Imogen', 'Lola', 'Matilda', 'Elizabeth', 'Layla', 'Alasdair','Holly', 'Lilly', 'Molly', 'Erin', 'Ellie', 'Maisie', 'Maya', 'Abigail', 'Eliza', 'Georgia', 'Jasmine', 'Esme', 'Willow', 'Leanne', 'Bella', 'Annabelle', 'Keemiya', 'Ivy', 'Amber', 'Emilia', 'Emma', 'Summer', 'Hannah', 'Eleanor', 'Harriet', 'Rose', 'Amelie', 'Lexi', 'Megan', 'Gracie', 'Zara', 'Nuha', 'John', 'Lacey', 'Martha', 'Anna', 'Violet', 'Darcey', 'Maria', 'Maryam', 'Brooke', 'Aisha', 'Katie', 'Leah', 'Heinrich', 'Nour', 'Thea', 'Darcie', 'Hollie', 'Amy', 'Alexandra', 'Stephen', 'Jonathan', 'Penny', 'Mollie', 'Heidi', 'Lottie', 'Bethany', 'Francesca', 'Faith', 'Harper', 'Nancy', 'Beatrice', 'Isabel', 'Juliette', 'Darcy', 'Lydia', 'Sarah', 'Sara', 'Julia', 'Victoria', 'Zoe', 'Robyn', 'Oliver', 'Jack', 'Harry', 'Jacob', 'Charlie', 'Thomas', 'Annabel', 'George', 'Oscar', 'James', 'Ian', 'William', 'Noah', 'Alfie', 'Joshua', 'Yuvraj', 'Muhammad', 'Leo', 'Archie', 'Ethan', 'Joseph', 'Arushi', 'Freddie', 'Samuel', 'Alexander', 'Logan', 'Daniel', 'Isaac', 'Max', 'Mohammed', 'Benjamin', 'Hugo', 'Mason', 'Lucas', 'Edward', 'Harrison', 'Jake', 'Neil', 'Dylan', 'Asher', 'Riley', 'Akash', 'Finley', 'Catherine', 'Theo', 'Muktarsi', 'Sebastian', 'Adam', 'Zachary', 'Arthur', 'Thomas', 'Alberto', 'Toby', 'Jayden', 'Luke', 'Harley', 'Lewis', 'Tyler', 'Harvey', 'Anusha', 'Matthew', 'David', 'Reuben', 'Alok', 'Michael', 'Elijah', 'Kian', 'Tom', 'Mohammad', 'Blake', 'Jean', 'Luca', 'Theodore', 'Stanley', 'Derin', 'Jenson', 'Nathan', 'Nicholas', 'Charles', 'Frankie', 'Constantin', 'Jude', 'Teddy', 'Eric', 'Viren', 'Louie', 'Louis', 'Ryan', 'Hugo', 'Bobby', 'Niamh', 'Anya', 'Elliott', 'Dexter', 'Khai', 'Hariesh', 'Henry', 'Ollie', 'Aron', 'Alex', 'Liam', 'Kai', 'Gabriel', 'Connor', 'Aaron', 'Afrah', 'Frederick', 'Callum', 'Lorcan', 'Elliot', 'Albert', 'Leon', 'Ronnie', 'Rory', 'Jamie', 'Austin', 'Seth', 'Ibrahim', 'Mei', 'Owen', 'Caleb', 'Yousuf', 'Ellis', 'Sonny', 'Devyn', 'Robert', 'Joey', 'Felix', 'Finlay', 'Rossa', 'Ekraj', 'Jackson', 'Jimi', 'Meera', 'Rafi', 'Salahdeen', 'Guido', 'Tanya', 'Karlis']
You’ll have to scroll sideways in the code block to view all the names, as there are quite a few. Although you could copy and paste this code into a new file in your IDE to proceed, there is also another option. As you work your way through this book, you’ll need access to some files. Rather than copy-pasting from here each time, you can download the repository of files you need.
Download The Python Coding Book File Repository
Through the link above, you can download the folder you need directly to your computer. I would recommend this option which is the most straightforward. But if you prefer, you can also access the repository through Github.
NOTE: As the content of The Python Coding Book is currently being gradually released, this repository is not final, so you may need to download it again in the future when there are more files that you’ll need in later chapters.
Making files accessible to your project
The simplest way to make sure you can access a file from your Python project is to place it in the project folder—the same folder where your Python scripts are located. If you’re using an IDE such as PyCharm, you can drag a file from your computer into the Project sidebar to move the file.
Alternatively, you can locate the folder containing your Python scripts on your computer and simply move the files you need in that folder as you would move any other file on your computer.
Tip: In PyCharm, if the Project sidebar is open you can click on the project name (top line) and then show the contextual menu with a control-click (Mac) or right-click (Windows/Linux). One of the options will be Reveal in Finder or Show in Explorer depending on what operating system you’re using.
You’ll need the file names.py
from the file repository for this project. You’ll need to place this file in your project folder.
Your task is to find all names that start with the letter P. The first step is to look at the first name. In the previous chapter, you learned about indexing and how you can extract a single item from a list:
# list_of_names = [...] name = list_of_names[0] print(name)
Note: In all the code blocks for this project, I will not show the first line in which the list
is assigned to list_of_names
. This will avoid the very long first line that affects how the code block is displayed. Instead, the line will be replaced by a comment—the line that starts with a #
. The assignment needs to be present in your code, though.
The index 0
represents the list’s first item, which the program will store in the variable name
. The output from the code above displays the first name in the list:
Amelia
You now want to retrieve the first letter of this name so that the program can decide whether this is the required letter. The variable name
is a string. When you learned about indexing to access items from a list, I mentioned that indexing works on other data types. Any data types which are sequences can be indexed. A string is a sequence of characters, and you can therefore use the same indexing techniques as you did with lists:
print(name[0])
You’re extracting the first item from the string name
:
A
It’s decision time now. Your program must decide what to do with this name. You’ll therefore need to use an if
statement:
# list_of_names = [...] name = list_of_names[0] if name[0] == "P": print(name)
Since the first name is Amelia which does not start with the letter P, the program doesn’t output anything. However, you should check that your code does work. You can change the "P"
into an "A"
in the conditional statement to check that the name Amelia is displayed.
If you look back at the steps you wrote in the planning stage, you’ll find that you now need to repeat the same thing for the second name in the list, and then the third and so on. Your program now needs to repeat code. Since you want to repeat code for all the names in the list, whatever happens, you’ll need to use a for
loop.
It’s possible to use the for
loop with the range()
function and then change the number you’re using when you index list_of_names
to get a different name from the list each time. However, there’s a more Pythonic way of writing this loop. You can iterate directly through the list:
# list_of_names = [...] for name in list_of_names: # name = list_of_names[0] if name[0] == "P": print(name)
The variable name
is now defined in the for
statement. Therefore each item in the list will be assigned to it, one at a time. The assignment name = list_of_names[0]
is no longer needed. This action is now being taken directly as part of the for
loop.
This assignment has been commented out in the code above by adding the hash symbol #
before it. Any code that follows the hash symbol #
is ignored by the computer program and will not be executed.
You can safely delete this line as it doesn’t have any effect on the code. The line is left in place but commented out in the code above to serve as a reminder of the steps you’ve taken to get to this point. The result when this code is run is the following:
Poppy
Phoebe
Penny
There are only three names that start with the letter P in the long list of names you began with. At the moment, these names are displayed as an output. If you wanted to store these names in the program to use them later on in the code, you could place them in a list.
However, you can’t do this after the for
loop finished iterating through the list. It will be too late by then as these names are not being stored. The program ‘forgets’ that these names are important as soon as the for
loop moves on to the next name.
Therefore, you have to store these names the moment the program identifies them as the names you want to keep. The way you can do this is shown below:
# list_of_names = [...] result = [] for name in list_of_names: if name[0] == "P": result.append(name) print(result)
You start by creating an empty list and assign it to the variable result
. This variable is not empty. It has a list that is empty. This difference may seem just a technicality, but it’s an important distinction. The program can now identify result
as data of type list
. Even though the list is empty, it’s still a list.
The other change in the code is the line that follows the if
statement. Instead of printing the name if it starts with a P, you’re adding it to the list:
result.append(name)
You’ll recognise append()
as a function as it’s written in lowercase letters and it’s followed by parentheses. The contents of name
are added to the end of the list result
. The style of this line bears some resemblance to another expression you’ve used before, random.randint()
. However, there is a significant difference.
The name random
refers to a module, a book the program fetches from the library when you use the import
keyword. The name result
refers to a variable which is a storage box where you’re storing a list.
You’ll read more about this structure in the next chapter when you’ll explore data types further. You’ll learn about the actions such as append()
that you can perform on different data types.
Defining Functions
The lines of code you’ve written are helpful to filter the list of names and find the names that start with the letter P. You can also find the names that begin with the letter N or A or any letter you wish. This seems like code that you may want to reuse often. It would be convenient if Python had a function called, let’s say, find_names_starting_with()
that you can use whenever you want to filter a list of names based on the first letter of the names. Unfortunately, there is no such function in Python. Not yet, at least.
You can create your own function called find_names_starting_with()
. You can create a new function using the def
keyword, which stands for define:
# list_of_names = [...] def find_names_starting_with(): result = [] for name in list_of_names: if name[0] == "P": result.append(name)
In the first line, you’re ‘teaching’ your computer program a new word, and more specifically, a function. You can choose any name you wish for the new function. You should follow modern best practices and choose a name that clearly describes what the function does.
Functions perform an action. Therefore, a function name should start with a verb to make its name clear. By convention in Python, function names are written using lowercase letters and with an underscore separating words. Note the parentheses and the colon to finish this first line.
The indented block of code following the colon is the definition of the new word you’ve just created. These lines are the code that the program will run whenever you use this function.
The function definition teaches the computer program the new word you’ve just created. You’re letting your program know that whenever you type find_names_starting_with()
later on in the program, these are the lines of code you want it to execute.
If you want the function to be executed, you’ll need to call the function. Calling a function is when you write the function name followed by parentheses. The parentheses ask the program to execute the function:
# list_of_names = [...] def find_names_starting_with(): result = [] for name in list_of_names: if name[0] == "P": result.append(name) find_names_starting_with()
When you run the code above, the program will run in the following order:
def find_names_starting_with():
- This line is executed first. It tells the program that you’d like to define a function called
find_names_starting_with()
. The function definition is not executed at this time.
- This line is executed first. It tells the program that you’d like to define a function called
find_names_starting_with()
- The program skips the indented block in the definition and moves to the next line. The next line is the function call. The program recognises this name as a function since you’ve defined it earlier. In this case, you defined the function in the lines immediately preceding the call, but the definition could have happened much earlier in the code.
- indented block inside function definition
- The program now goes back to where the function was defined, and the code in the function definition is executed.
Try running this code. You’ll get no output from this program. Perhaps it’s because you’re not printing the list. Let’s see what happens when you try to print result
:
# list_of_names = [...] def find_names_starting_with(): result = [] for name in list_of_names: if name[0] == "P": result.append(name) find_names_starting_with() print(result)
Running this code will give an error:
Traceback (most recent call last):
File "<path>/<filename>.py", line 10, in <module>
print(result)
NameError: name 'result' is not defined
This error is one you’ve seen before. Python is saying that it has no reference of anything named result
. The computer program looks all over the White Room and cannot find the result
anywhere. But you’re sure you’ve created the variable earlier on in the code. Why can’t the program find it?
Local Variables and Scope
Functions are sometimes referred to as sub-routines. I prefer the term mini-program. A function is a self-contained, small program that the main program can call. Another function or another program can also call it.
Since a function is a self-contained unit of code, any variable created inside the function stays inside the function. These variables are not available outside of it. This fact is why you get the error message saying that name 'result' is not defined
. The variable result
does not exist in the main program area because it only exists inside the function.
This is called the scope. The scope of a variable refers to those parts of a program where the variable exists. A variable created in the main program exists everywhere in the program, including within any functions defined in the program. This is a global variable, and its scope is the whole of the program. But a variable created inside a function is a local variable. Its scope is limited to within the function.
Confused? This topic is not the most straightforward one. And that’s an understatement. We’ll discuss this further later in this Chapter.
Returning Data
Back to the Finding Names project code. You’ve defined a function that created the result you want. The problem is that the variable containing the list with the names required seems to be trapped inside the function. However, when a function finishes all the actions it needs to do, it can take some data back into the main program. You can send data from the function back to the main program using a return
statement:
# list_of_names = [...] def find_names_starting_with(): result = [] for name in list_of_names: if name[0] == "P": result.append(name) return result find_names_starting_with() print(result)
Note the indentation level for the return
statement. It’s part of the def
block but not part of the for
or if
blocks. Therefore it only needs one indent.
You’re not there yet—there’s still an error when you run this code. Let’s see why.
The return
statement in the function definition tells the function to take whatever data is stored in result
back into the main program where the function was called. Here’s a bit of pedantry: The function does not return the variable but the data contained within that variable. The storage box is not returned to the main program, but only the contents of the box are.
This distinction is the reason why you get the following error when you run the latest version of the code:
Traceback (most recent call last):
File "<path>/<filename>.py", line 12, in <module>
print(result)
NameError: name 'result' is not defined
The find_names_starting_with()
function did not return the box labelled result
to the main program. Therefore the main program cannot find any reference to result
. The function returned the contents of the box, which is a list containing strings. This list needs to be stored in a new box if you want to keep this information. Otherwise, the information is lost. You need to assign the data that’s returned from the function to a variable:
# list_of_names = [...] def find_names_starting_with(): result = [] for name in list_of_names: if name[0] == "P": result.append(name) return result result = find_names_starting_with() print(result)
You have now created a box called result
in the main program, and you’ve used it to collect the data that’s returned from find_names_starting_with()
. This concept is the same as when you collected the data returned from input()
in a variable when you used input()
in the Angry Goblin game.
The result
within the function and the result
created in the main program to store the data returned from the function are different variables. They are two separate boxes, and there’s no reason they need to have the same label. This code will be clearer if you use a different label for the two boxes:
# list_of_names = [...] def find_names_starting_with(): result = [] for name in list_of_names: if name[0] == "P": result.append(name) return result names_p = find_names_starting_with() print(names_p)
You’ll now get an output from this code:
['Poppy', 'Phoebe', 'Penny']
Parameters And Arguments
You’ve converted the code that filters the names based on their first letter into a function. You can now reuse this code whenever you want by calling the function.
You can make this function more flexible. At the moment, the function can only filter names that start with the letter P. It’s not helpful if we’re looking for names beginning with A.
The first line in a function definition is called the function signature. You can rewrite the function signature as follows:
def find_names_starting_with(letter):
You’re still asking the program to learn a new function name with this line, but you’re now letting the program know that there will be some information in the parentheses when you call the function. This information is stored in a box labelled letter
within the function. The name letter
is called a parameter.
You’ll also need to modify the function call:
names_p = find_names_starting_with("P")
When you call a function, the data you place in the parentheses are called arguments. The distinction between parameters and arguments is subtle but important. The parameter name is the name used in the function definition to refer to data. The argument is the actual value passed into the function when it’s called. In this example:
- The parameter is
letter
- The argument is
"P"
When a function is called, a box is created in the function labelled with the parameter name. The value passed as an argument is put inside the box. Sounds familiar? The parameter name becomes a variable within the function.
Note how choosing a good name for the function can make a big difference to how readable your code is. Reading out the function call, you get Find names starting with P, which clearly describes the action performed by the function.
There’s one more change you need to make to the function definition:
# list_of_names = [...] def find_names_starting_with(letter): result = [] for name in list_of_names: if name[0] == letter: result.append(name) return result names_p = find_names_starting_with("P") print(names_p)
You no longer need to check whether name[0]
is equal to the string "P"
. Instead, you can use the label of the box. This label is the parameter name. You can now call the function as often as you wish, using different input arguments:
# list_of_names = [...] def find_names_starting_with(letter): result = [] for name in list_of_names: if name[0] == letter: result.append(name) return result names_p = find_names_starting_with("P") print(names_p) names_a = find_names_starting_with("A") print(names_a) names_b = find_names_starting_with("B") print(names_b)
You’ve called the function three times with "P"
, "A"
, and "B"
as input arguments, and you’ve assigned the lists returned by each function call to different variables. The output shows all three lists:
['Poppy', 'Phoebe', 'Penny']
['Amelia', 'Alexey', 'Ava', 'Alice', 'Alasdair', 'Abigail', 'Annabelle', 'Amber', 'Amelie', 'Anna', 'Aisha', 'Amy', 'Alexandra', 'Annabel', 'Alfie', 'Archie', 'Arushi', 'Alexander', 'Asher', 'Akash', 'Adam', 'Arthur', 'Alberto', 'Anusha', 'Alok', 'Anya', 'Aron', 'Alex', 'Aaron', 'Afrah', 'Albert', 'Austin']
['Bella', 'Brooke', 'Bethany', 'Beatrice', 'Benjamin', 'Blake', 'Bobby']
You can get more practice by writing another function, find_names_of_length()
, which filters the names in the list based on the number of letters in the name. You can try writing this function, but first, you’ll need to learn another built-in function, len()
:
>>> my_numbers = [4, 6, 12, 9, 6, 23] >>> len(my_numbers) 6 >>> name = "Stephen" >>> len(name) 7
The function len()
returns the length of any data type that’s a sequence. When you use it on a list, it will return the number of items in the list, and when you use it on a string, it returns the number of characters.
Before you carry on reading, try to write the function find_names_of_length()
.
Have you finished your attempt? You can read on.
Here’s the code showing both functions you’ve written so far:
# list_of_names = [...] def find_names_starting_with(letter): result = [] for name in list_of_names: if name[0] == letter: result.append(name) return result def find_names_of_length(length): result = [] for name in list_of_names: if len(name) == length: result.append(name) return result names_p = find_names_starting_with("P") print(names_p) names_9 = find_names_of_length(9) print(names_9)
This code displays the following output:
['Poppy', 'Phoebe', 'Penny']
['Charlotte', 'Elizabeth', 'Annabelle', 'Alexandra', 'Francesca', 'Alexander', 'Catherine', 'Sebastian', 'Frederick', 'Salahdeen']
The output shows two lists. The first list has all names starting with P, and the second list has all names with 9
letters. What if you want to find all names that start with E and are six letters long? You could write another function that combines both features, which you may call find_names_starting_with_and_of_length(letter, length)
. However, there’s a better way.
One of the principles you should remember when defining functions is that a function should perform only one action. When you describe what a function does, there should be no and in that description as the function should perform only one task. In the next section, you’ll extend the functions you’ve defined above to make them even more useable and flexible than they already are.
More Parameters, More Arguments
Earlier in this Chapter, you’ve seen that when you define a variable inside a function definition, the variable is only available within the function. It’s not accessible from the main program. In Python, the reverse is not true. You have used the variable list_of_names
inside the function definitions even though you created this variable in the main program. Although you can do this, there’s a better way.
You can define your function so that it doesn’t have to rely on a variable that exists in the main program. Instead, you can send all the information needed by a function when you call it.
You can modify your functions as follows:
# list_of_names = [...] def find_names_starting_with(letter, names): result = [] for name in names: if name[0] == letter: result.append(name) return result def find_names_of_length(length, names): result = [] for name in names: if len(name) == length: result.append(name) return result
Each function has two parameters now. In the parentheses in the first line of both function definitions, you’ve added a parameter which you’ve called names
. When you call the function, you need to pass two separate bits of information now instead of one.
Since within the function definitions, the name of the list containing the names is no longer list_of_names
but the parameter names
, you’ll need to modify the for
statements as well.
When you call find_names_starting_with()
, you’ll have to pass a string with the letter you want to filter with and the list of names you want to use. With find_names_of_length()
, the two arguments are the length required and the list of names to use.
You can now call the functions and print the lists that each function returns:
# list_of_names = [...] def find_names_starting_with(letter, names): result = [] for name in names: if name[0] == letter: result.append(name) return result def find_names_of_length(length, names): result = [] for name in names: if len(name) == length: result.append(name) return result names_p = find_names_starting_with("P", list_of_names) print(names_p) names_9 = find_names_of_length(9, list_of_names) print(names_9)
The output from this code is the same as for the version in the previous section. However, this latest version of the function definitions makes these functions a lot more flexible as you can now use them with any list that has names in it and not just the one created at the top of the program.
Let’s try and find all names that start with an E and that are six letters long. You can first call find_names_starting_with()
in the same way as you did earlier:
# list_of_names = [...] def find_names_starting_with(letter, names): result = [] for name in names: if name[0] == letter: result.append(name) return result def find_names_of_length(length, names): result = [] for name in names: if len(name) == length: result.append(name) return result names_e = find_names_starting_with("E", list_of_names) print(names_e)
All the names starting with E are displayed:
['Emily', 'Evie', 'Ella', 'Eva', 'Evelyn', 'Elsie', 'Elizabeth', 'Erin', 'Ellie', 'Eliza', 'Esme', 'Emilia', 'Emma', 'Eleanor', 'Ethan', 'Edward', 'Elijah', 'Eric', 'Elliott', 'Elliot', 'Ellis', 'Ekraj']
You can now call the find_names_of_length()
function. However, instead of passing the full list as an argument, you can pass names_e
. You’re using the result of one function as an input for the next:
# list_of_names = [...] def find_names_starting_with(letter, names): result = [] for name in names: if name[0] == letter: result.append(name) return result def find_names_of_length(length, names): result = [] for name in names: if len(name) == length: result.append(name) return result names_e = find_names_starting_with("E", list_of_names) names_e_and_6 = find_names_of_length(6, names_e) print(names_e_and_6)
The output shows a list with all the names starting with E that are six letters long:
['Evelyn', 'Emilia', 'Edward', 'Elijah', 'Elliot']
Functions are at their best when you can use them in a flexible way. They are a way of packaging code so that you can reuse it whenever needed. Functions are a key component for writing DRY code. Defining functions is probably one of the most powerful tools in modern programming.
Error Messages When Calling Functions
The signature of the find_names_starting_with()
function now looks like this:
def find_names_starting_with(letter, names):
This line tells your computer program that there’s a function called find_names_starting_with
and that when you call this function, you’ll supply two bits of information as arguments in the parentheses. These are required arguments. Let’s see what happens if you try to call the function without these required arguments:
names_e = find_names_starting_with()
This line gives the following error messages:
Traceback (most recent call last):
File "<path>/<filename>.py", line 1, in <module>
names_e = find_names_starting_with()
TypeError: find_names_starting_with() missing 2 required positional arguments: 'letter' and 'names'
You’ll get a similar error if you pass only one of the two required arguments:
names_e = find_names_starting_with("E")
The error message now complains about the 1
missing argument:
Traceback (most recent call last):
File "<path>/<filename>.py", line 1, in <module>
names_e = find_names_starting_with("E")
TypeError: find_names_starting_with() missing 1 required positional argument: 'names'
The two error messages mention missing required positional arguments
. You’ve seen why they’re referred to as required. You’ll learn about optional arguments later on.
The arguments are also described as positional because there are different ways you can pass information into the function. In the function call you’ve used so far, the data are assigned to a parameter based on the position of the arguments in the parentheses:
names_e = find_names_starting_with("E", list_of_names)
The string "E"
is the first argument, and therefore it’s assigned to the parameter letter
. The variable list_of_names
is the second argument, and the contents of this box are assigned to the parameter names
.
However, you can choose to name the arguments in a function call:
names_e = find_names_starting_with(letter="E", names=list_of_names)
These are called keyword arguments. You’re using the parameter name to identify the arguments. When you use keyword arguments, the order in which you include the arguments in a function call is not important:
names_e = find_names_starting_with(names=list_of_names, letter="E")
This function call works perfectly even though the order of the arguments is not the same as the order of the parameters in the function signature.
You can use a mixture of positional and keyword arguments. However, the positional arguments must come first. The following line is valid:
names_e = find_names_starting_with("E", names=list_of_names)
The argument "E"
is a positional argument, whereas list_of_names
is passed as a keyword argument. However, if you try to pass a keyword argument first followed by a positional argument, you’ll get an error:
names_e = find_names_starting_with(letter="E", list_of_names)
The error message is the following one:
File "<path>/<filename>.py", line 1
names_e = find_names_starting_with(letter="E", list_of_names)
^
SyntaxError: positional argument follows keyword argument
The error message shows that this is a SyntaxError
. You can think of syntax as the grammar of the coding language. The error clearly states that the problem here is that the positional argument follows keyword argument
.
You’ll get errors often when you’re writing code. Even proficient coders get errors. Learning to recognise common error messages and understanding them is an important skill to learn and master. Unfortunately, not all error messages are clear, but they will always give you a clue about the problem.
The White Room Revisited
The previous Chapter introduced you to The White Room analogy. The White Room represents the computer program. There’s a set of shelves that include:
- The ‘built-in’ booklet with all the functions and keywords that every program has access to automatically
- Books brought from the library using the
import
keyword - Boxes with labels and some content inside the boxes. These boxes represent variables
When you type any word in your code, the computer program looks around the White Room to find a reference to that name. What about new functions you define? How do these fit in this analogy?
I introduced functions as mini-programs earlier in this Chapter. A function is a self-contained unit that performs a specific action, not unlike an entire computer program. For this reason, you can think of a function like another room with a specific purpose.
When you define a function within a program, you’re creating a new room adjacent to the White Room, with a door connecting the two. The name you give the function is the label you put on the door of the Function Room. When you type a function’s name, the computer program will look around the White Room and find a reference to this name as a label on a door leading to another room.
When you call a function:
- The program leaves the White Room and goes through the door leading to the Function Room
- It performs whatever actions are needed in the Function Room
- The program returns to the main White Room when it completes these tasks. It shuts the door to the Function Room on the way out
Let’s use one of the function definitions from the Finding Names project to develop this analogy further:
def find_names_starting_with(letter, names): result = [] for name in names: if name[0] == letter: result.append(name) return result
The variable result
is a local variable in the function. When you define a variable created inside a function definition, the box that’s created is placed on the shelves inside the Function Room. This is why this variable is not accessible from the main program. It’s not present in the White Room but only in the Function Room.
Parameters and Arguments
At the entrance of the Function Room, there are two empty boxes ready to be used. They’re labelled letter
and names
. The parameters are empty storage boxes ready to be filled in as soon as the program enters the Function Room.
When you call a function, the arguments are taken to the Function Room and put inside the parameter boxes. These boxes are then placed on the shelves in the Function Room. They’re ready to be used when needed in the function:
names_e = find_names_starting_with("E", list_of_names)
This function call leads to the following steps:
- The program finds a door labelled
find_names_starting_with
that leads to a Function Room - It ‘picks up’ a
str
containing the letter"E"
- It locates the box
list_of_names
which it finds in the White Room. It doesn’t take the box but only its contents - The program then goes through the door labelled
find_names_starting_with
into the Function Room, taking the information it collected along with it - As soon as the program enters the Function Room, it will find the two empty parameter boxes waiting to be filled. The data the program takes into the Function Room are placed in these boxes
Return Statement
You’ve seen how variables created within the function are local to the function. The box only exists in the Function Room.
When the function completes its actions, the program is ready to leave the Function Room and return to the White Room. Before it leaves the Function Room, it takes some information along with it back to the White Room. The return
statement determines what these data are.
In the find_names_starting_with
Function Room, there’s a box labelled result
. This variable is included in the return
statement. When the program finishes from the Function Room and is ready to return to the main White Room, it doesn’t take the result
box back with it, but only its contents. This subtle but important distinction is why the main program cannot use the variable result
. There’s still no box labelled result
in the White Room.
However, the function call in the main program has an assignment statement:
names_e = find_names_starting_with("E", list_of_names)
You’re now creating a box in the main White Room labelled names_e
and putting whatever the program brought back from the Function Room into this box. The data returned from the function is now available to be used in the main program.
When a function finishes all its actions:
- It collects the data from the boxes named in the
return
statement - It leaves the Function Room and takes the data along with it to the White Room
- Once in the White Room, the program creates a new box and stores the data it brought from the function in this box
Even functions that you didn’t write yourself are Function Rooms your program can access. When you type print()
, the program finds a reference to this function in the ‘built-in’ booklet. The booklet provides a map of where the print
Function Room is within the Python city. The program will leave the White Room, go for a stroll through the Python city until it reaches the print
Function Room, perform the actions needed, and then go back to the White Room.
The map showing the program where to find the randint()
Function Room is in the book named random
, which you brought from the library with the import
keyword.
A typical computer program will start in the main White Room and will then move across several other rooms, going in and out, performing actions, and moving data from one room to the next.
Conclusion
In this Chapter, you’ve covered:
- How functionality in a program can be categorised as Store, Repeat, Decide, and Reuse
- How to define your own functions
- What’s the difference between local and global variables
- How to return data from a function
- How to include parameters when defining a function
- How to use positional and keyword arguments
Defining functions is a powerful tool in programming. It allows you to write code that you can reuse in a flexible way as and when you need to. Another key component of all computer programs is the data that’s used and created. In the next Chapter you’ll learn more about data and data types.
Subscribe to
The Python Coding Stack
Regular articles for the intermediate Python programmer or a beginner who wants to “read ahead”
Coming Soon…
The Python Coding Place
Sign-Up For Updates
The main text of the book is complete—I’m planning some additions and exercises and I’ll make this book available in other formats soon. Blog posts are also published regularly.
Sign-up for updates and you’ll also join the Codetoday Forum where you can ask me questions as you go through this journey to learn Python coding.