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.
Become a Member of
The Python Coding Place
Video courses, live cohort-based courses, workshops, weekly videos, membersâ forum, and moreâŚ
Subscribe to
The Python Coding Stack
Regular articles for the intermediate Python programmer or a beginner who wants to âread aheadâ