Python 4.1Free video15 minLesson 1 of 4 in Module 4
Defining Functions
Package code into reusable functions with inputs and outputs.
Free sample. Everything in this module opens with a free account.
Watch or read the lesson: Defining Functions
01Why functions?
In the previous module, you built a simple tokenizer. Remember that product review about the kettle? You wrote a few lines of Python to clean the text. First, you removed the extra spaces from the beginning and end. Then you converted everything to lowercase. After that, you removed a few punctuation marks. And finally, you split the text into individual words.
Here is that code again, run on the kettle review:
review = " Great kettle! Boils fast, and it is QUIET. "
text = review.strip().lower()
for mark in ".,!?":
text = text.replace(mark, "")
words = text.split()
words
['great', 'kettle', 'boils', 'fast', 'and', 'it', 'is', 'quiet']
You get eight words: great, kettle, boils, fast, and, it, is, and quiet. Exactly what you expected.
A second review
Now imagine another customer leaves a review. This one says: "Too loud, returned it, sadly." You want to clean that review too.
You could copy the original code into another cell, replace the old review with the new one, and run everything again:
review = "Too loud, returned it, sadly."
text = review.strip().lower()
for mark in ".,!?":
text = text.replace(mark, "")
words = text.split()
words
['too', 'loud', 'returned', 'it', 'sadly']
And it works. You get five words: too, loud, returned, it, and sadly. So far, so good.
The problem with copying
But here is a question. What happens when you have a hundred reviews? Are you going to copy and paste those same lines a hundred times?
And what if you discover a mistake in your cleaning logic? Maybe you forgot to handle a punctuation mark. Now you would have to find every place you copied that code and fix it in each one. That does not sound like a good approach.
Write it once, use it many times
What you really want is a way to write those lines once, give that block of code a name, and run it whenever you need it, on whichever review you want. That is exactly what a function allows you to do.
In this lesson, you will take the tokenizer and turn it into a function called tokenise. The cleaning logic stays in one place, and you can reuse it as many times as you need. Along the way, you will learn how to define a function, how to call it, what happens when Python executes it, and how it returns a result.
02Defining and calling
Start by building the tokenizer function from scratch.
The header
First, you write the keyword def. That is short for define, and it tells Python you are defining a function. After def, you write the function's name, in this case tokenise. Then you add a pair of parentheses, and inside those parentheses you write review. Finally, you add a colon. So the first line looks like this:
def tokenise(review):
Here, review is called a parameterKey termThe name in a function's definition that a value will be bound to: price in def with_tax(price):.. A parameter is a name that the function uses to refer to a value it receives. In this case, that value will be the review you want to clean.
The body
Underneath that first line, you write the function body. Just like the body of an if statement or a for loop, it needs to be indented. Inside the body goes the same cleaning logic you wrote before: strip the extra whitespace, convert the text to lowercase, loop through the four punctuation marks and remove them, then split the cleaned text into words. And finally, you write return words. You will come back to that return statement in a moment. For now, here is the complete function:
def tokenise(review):
text = review.strip().lower()
for mark in ".,!?":
text = text.replace(mark, "")
words = text.split()
return words
What def does
Now run this cell. What happens? Nothing appears. No list of words, no printed message, not even an error. So did Python actually do anything?
Yes. But it did not run the cleaning code yet. When Python executes a def statement, it creates a function objectKey termAn object created by a function definition or lambda, which can be passed, stored and called. and binds the name tokenise to that object. Remember what you learned about variables: a name refers to an object. And a function is also an object in Python. So if you check its type, Python tells you it is a function:
type(tokenise)
function
The important thing is that defining a function does not execute its body. The cleaning steps are there, and Python knows about them, but they will not run until you call the function. That is the difference between defining a function and calling one.
Calling a function
So how do you call it? You write the function's name, followed by parentheses. Inside those parentheses, you provide the value you want the function to work on, for example tokenise(review).
Here is a technical distinction worth understanding. The name review inside the function definition is a parameter. But the actual value you pass when calling the function is called an argumentKey termA value passed to a function in a call: 12.50 in with_tax(12.50).. So the parameter is the name written in the definition, and the argument is the value supplied during the call.
You have actually been passing arguments throughout this course. Every time you wrote print("Hello"), you passed a string as an argument to print. Every time you used round or any other function, you passed arguments to it. Now you are doing the same thing with a function you have written yourself.
Two reviews, one function
Call tokenise with the kettle review:
tokenise(" Great kettle! Boils fast, and it is QUIET. ")
['great', 'kettle', 'boils', 'fast', 'and', 'it', 'is', 'quiet']
You get the same eight clean words as before. Now call it with the second review:
tokenise("Too loud, returned it, sadly.")
['too', 'loud', 'returned', 'it', 'sadly']
And you get five words. The cleaning code is written once, and it works on whichever review you pass in.
03Inside a function call
Now look at what actually happens inside Python when you call a function, because this is one of the most important ideas in this lesson. To make it easier to follow, use a smaller example.
Remember the restaurant receipt? You had a price of $12.50 for pasta, and you wanted to add eight percent tax. Write a function called with_tax. It takes one parameter called price, and returns the price after adding eight percent tax, rounded to two decimal places:
def with_tax(price):
return round(price * 1.08, 2)
The call, step by step
Now call it with 12.50 and follow what Python does, step by step.
- First, Python evaluates the argument. In this case, the argument is simply the number
12.50. - Then Python creates a new execution frame for this function call. A frame is the context Python uses to keep track of one particular function call. It includes the function's local names and information about where execution is happening.
- Inside that frame, Python binds the parameter
priceto the argument you passed. So for this call,pricerefers to12.50. - Then Python starts executing the function body. It calculates
12.50 * 1.08, which gives13.5. Then it rounds that result to two decimal places. - Finally, it reaches the
returnstatement.
return does two things. It ends the function call, and it gives the specified value back to the code that called it. So the expression with_tax(12.50) produces 13.5, and the code that called the function can use that result:
with_tax(12.50)
13.5
Once the call finishes, its execution frame is no longer active. The local parameter price from that call is no longer available as a local name in the caller's scope.
A second call
What happens if you call the same function again, but with a different price? For example, what does with_tax(8.25) give? Think it through before reading on.
I have a guess. Show meHide the answer
with_tax(8.25)
8.91
You get 8.91, because 8.25 times 1.08 is 8.91.
Notice something important. This is a completely new function call. Python creates a new execution frame, and this time the parameter price refers to 8.25. Every call has its own execution context. The function does not permanently remember the previous value of price. It uses whichever argument you provide for that particular call.
Calls inside expressions
Because the function returns a value, you can combine several calls inside a larger expression. For example, with two calls:
with_tax(12.50) + with_tax(6.00)
19.98
Each call produces its value, 13.5 and 6.48, and the + adds them. So function calls behave like expressions that produce values.
04Return, print and None
That brings you to an important distinction: the difference between return and print. This is something beginners often confuse.
Create another function called show_tax. Instead of returning the calculated price, it prints it:
def show_tax(price):
print(round(price * 1.08, 2))
Now call it and see the result:
show_tax(12.50)
13.5
You see 13.5 printed below the cell. It looks like everything is working.
Storing the result
But here is a question. What happens if you try to store the result? Assign the call to a name, total:
total = show_tax(12.50)
13.5
What do you think total contains? Is it 13.5, or something else? Think it through before reading on.
I have a guess. Show meHide the answer
total
The cell shows nothing at all. The function printed 13.5, but total is actually None, and a notebook displays nothing for None. You can confirm it:
total is None
True
Why? Because print() only displays a value. It does not return that displayed value to the caller. And show_tax has no return statement, so when Python reaches the end of its body, it automatically returns None. Remember, None represents the absence of a value. So the function call produces None, and that is what gets assigned to total.
Using None in a calculation
Now try using total in a calculation:
total + 1
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
Python raises a TypeError, because you are trying to add an integer to None.
return or print?
So here is the distinction to remember. print() displays something. return gives a value back to the code that called the function. That returned value can be stored, passed to another function, or used in an expression.
When you are writing your own functions, think about what the function is supposed to do. If it needs to calculate something that other code will use, you probably want to return the result. If its purpose is simply to display something, print may be appropriate.
05Early return
There is another important thing about return. It does not just give a value back. It also stops the function immediately.
Suppose you write a function that prints the word Before, then returns the number 1, and after that has another print statement that would display After:
def example():
print("Before")
return 1
print("After")
What happens when you call it? Think it through before reading on.
I have a guess. Show meHide the answer
example()
Before
1
Python prints Before. Then it reaches return 1, and the function ends immediately. So the second print statement never runs, and you do not see After. The 1 below is the returned value, which the notebook displays because the call is the last expression in the cell.
return inside a loop
This behavior is especially useful inside loops. Remember the lesson on break and continue? You used break to stop a loop when you found something. Inside a function, return can do even more.
Revisit the spending example. You had five days of spending, and you wanted to find the first day when spending exceeded ninety dollars. Write a function called first_over. It takes two parameters: a collection of amounts, and a limit. It loops through the amounts, keeping track of the day number, and as soon as it finds an amount greater than the limit, it returns the day and the amount:
def first_over(amounts, limit):
for day, amount in enumerate(amounts, start=1):
if amount > limit:
return day, amount
Now call it with the spending data and a limit of ninety:
first_over([42.10, 18.75, 96.40, 12.00, 130.25], 90)
(3, 96.4)
The result is day three, with an amount of 96.40. Exactly what you found in the earlier lesson.
But here is the important part. As soon as Python reaches that return statement, the entire function ends. It does not continue checking the remaining days, so it never looks at 12.00 or 130.25. return stops both the loop and the function.
06Documentation and hints
Imagine someone else is using your code. Maybe they are working on another part of the project. They see a function called tokenise. But how do they know exactly what it does? Does it remove punctuation? Does it convert text to lowercase? Does it return a string or a list?
They could read through the function body. But there is a better way to document that information, using a docstringKey termA string on the first line of a function's body that says what the function does. help() and __doc__ show it.. A docstring is a string placed at the beginning of a function's body, and it describes what the function does. The usual convention is to write it with triple double quotes. Add one to the tokenizer:
def tokenise(review):
"""Return lowercase words; remove . , ! ?"""
text = review.strip().lower()
for mark in ".,!?":
text = text.replace(mark, "")
words = text.split()
return words
Reading documentation with help
Notice that the docstring is the first statement inside the function. Python recognizes it and stores it as the function's documentation. You can read that documentation with help():
help(tokenise)
Help on function tokenise in module __main__:
tokenise(review)
Return lowercase words; remove . , ! ?
Python displays information about the function, including its name, its parameters and its docstring. This is useful because you do not have to inspect every line of the function body to understand its purpose. The same help function works with Python's built-in functions too. So if you are ever unsure how a function works, try calling help on it.
Type hints
There is another way to make your functions easier to understand, and that is type hints. Remember, Python is dynamically typed: you do not normally have to declare the type of every variable. But sometimes it is useful to describe what type of value a function expects, and what type it returns.
Look at the tokenizer. You expect the review to be a string, and you expect the function to return a list of strings. So you can add type hints to the definition, like this:
def tokenise(review: str) -> list[str]:
"""Return the words of a review, lowercased, without . , ! or ?"""
text = review.strip().lower()
for mark in ".,!?":
text = text.replace(mark, "")
words = text.split()
return words
After the parameter review, there is a colon followed by str. That tells readers, and type-checking tools, that review is expected to be a string. Then, after the closing parenthesis, there is an arrow, ->, followed by list[str]. That indicates the function is expected to return a list containing strings. So type hints describe the intended types of a function's inputs and outputs.
help shows them too, as part of the function's signature:
help(tokenise)
Help on function tokenise in module __main__:
tokenise(review: str) -> list[str]
Return the words of a review, lowercased, without . , ! or ?
Python does not enforce type hints. As earlier lessons discussed, Python is dynamically typed, so a hint describes what the function expects without checking it when the function runs.
07Common mistakes
First, connect what you have learned to the functions you have been using throughout this course. Every time you called print, len, round, input or sorted, you were calling functions. But those functions were already provided by Python. They are called built-in functions. Functions like tokenise and with_tax are different: you wrote them yourself, so they are called user-defined functions.
Before finishing, look at a few common mistakes beginners make when working with functions.
Forgetting the parentheses
The first is forgetting the parentheses. Suppose you write just the name, with_tax, without parentheses:
with_tax
<function __main__.with_tax(price)>
Python does not call the function. It simply evaluates the function object itself, because the name with_tax refers to a function object, and the notebook displays that object. To actually execute its body, you need to call it using parentheses.
Leaving out a required argument
The second mistake is calling a function without providing a required argument. Here you have the parentheses, so you are trying to call the function, but you have not provided a price:
with_tax()
TypeError: with_tax() missing 1 required positional argument: 'price'
The function expects one required parameter, so Python raises a TypeError. It tells you that the required positional argument price is missing. You will explore different ways of passing arguments in the next lesson. For now, remember that a function call needs to provide the arguments required by its definition.
Running cells out of order
The third mistake is running notebook cells in the wrong order, which you met earlier in the course. A function only exists once its def cell has run. If you restart the notebook and run a cell that calls tokenise before running the cell that defines it, Python raises NameError: name 'tokenise' is not defined.
08Recap and challenge
You started with a few lines of code that cleaned a product review, and saw how copying those lines for every new review would quickly become repetitive. So you turned that code into a reusable function.
defcreates a function object and binds a name to it. The function body does not run until you call the function.- A parameter is the name written in the definition. An argument is the value supplied during the call.
- When you call a function, Python creates a new execution frame, binds the parameters to the supplied arguments, and runs the body.
returngives a value back to the caller, whileprintsimply displays something. If a function finishes without reaching areturnstatement, it returnsNone.returnimmediately ends a function, even when it is inside a loop.- Docstrings describe what a function does, and type hints describe the expected types of its parameters and return value.
Your challenge
Write a function called summary. It should take a list of numbers and return three values:
- the smallest number,
- the largest number,
- and the average, rounded to two decimal places.
Make sure your function has a docstring, and add type hints for its parameter and return value. For this challenge, you can assume the list contains at least one number.
Then call your function using the week of step counts from the earlier lessons, and unpack the three returned values into three separate variables:
week = [8200, 10450, 6300, 12000, 9100, 7400, 11050]
In the next lesson, you will learn how to make your functions more flexible.
Key terms: Defining Functions
The words this lesson introduces, each in one line. The module’s glossary collects them all, and the AI glossary has every term across the courses.
- argument
- A value passed to a function in a call:
12.50inwith_tax(12.50). - call
- Running a function's body, by writing its name and round brackets:
with_tax(12.50). - docstring
- A string on the first line of a function's body that says what the function does.
help()and__doc__show it. - frame
- The execution state for one call, including its local namespace and current instruction; its active call-stack entry ends when the call returns.
- function definition
- A
defstatement; running it creates a function object and binds its name. The body does not run until a call. - function object
- An object created by a function definition or lambda, which can be passed, stored and called.
- parameter
- The name in a function's definition that a value will be bound to:
priceindef with_tax(price):. - return value
- The value a call gives back. Reaching the end of the body normally without an explicit return gives back
None. - type hint
- A note in a function's header of the type a parameter should have, or the type it returns:
review: str,-> list[str]. Python does not check it when the code runs.
Quiz: Defining Functions
Your first pick on each question is the one that counts. Getting one wrong here is how the lesson sticks.