Review of functions
Recall that a function definition is a collection of code statements grouped together that perform a specific task.
Every function has a name that can be used to call the function. We also use the terms invoke, or execute to mean run the lines of code that appear within the function definition.
You can think of the function definition as a recipe for how to perform a task. When the function is invoked, then the task is actually performed.
Recall also that functions may take input values (called parameters) that modify or control how the function runs.
Why use functions?
There are two main reasons that functions are useful in programming:
-
Reusability
Often we need to use the same code multiple times. One solution is to copy/paste the code to reuse it. One downside of that strategy is that it makes our programs much longer. But an even worse downside is that if we make a modification, we need to find and make that same change in every place we copied the code to. Functions instead allow us to efficiently invoke the same code whenever needed without making multiple copies.
-
Abstraction
Once a function has been written and tested, we don't need to know how it works anymore. We only need to remember its name, purpose, parameters, and return value. For example, when you use the
dudraw.circle()
function, you can just use it without bothering your mind with the distraction of how that function creates the circle. This allows us to build functions from functions already written, and this allows us to produce very complicated software much more easily. -
Code organization and readability
Organizing your code into a sequence of function calls allows you to focus at any time while you are programming on just the task at hand. It also makes it far easier for others to read your code and understand it.
A function can return a value
A function can also return a result. You can choose the type of the returned value. If no value is returned, then the return type is None
.
For example, the print function, as in
print("Hello!")
takes a parameter (the input to the function is "Hello"
), but it does not return a value.
But the input()
function does return a value. The return type is str
. When the function is invoked, when it is done executing it returns a value. You may do whatever is useful with that return value. In the example below the value returned by the input()
function is stored in the variable name
:
name = input("Enter your name")
Another example is the random()
function, which returns a float
. We may choose to store the value that is returned in a variable. Or we may choose to pass that return value as a parameter to another function:
# Store the return value in a variable:
x_position = random()
# Pass the return value to the print function:
print(random())
User-defined functions that return values
When you define a function, you only need to do so once. Once defined, however, a function may be invoked many times. You can think of a function as an opaque box that performs work with optional inputs and an optional output.
The syntax is:
def function_name(optional parameters) -> return type:
# Indented code block
# Optional return statement
# (Leave off the return statement if the function returns `None`)
Example:
Here's a function that takes a float
parameter named temp_f
that represents a temperature in Fahrenheit (think of temp_f
as an input value to the function). The function then computes the conversion of the temperature to Celsius. The converted value temp_c
of type float
is returned (think of temp_c
as an output value from the function).
def celsius(temp_f: float) -> float:
# compute the conversion from fahrenheit to celsius
temp_c = (temp_f - 32) * 5 / 9
return temp_c
In the example above, think of the parameter temp_f
as a local variable of the celsius()
function. That variable can only be accessed within the scope of the function. When the function completes execution, the variable temp_f
is destroyed and can no longer be accessed. On the last line of the function, the return
statement has the effect of sending the value temp_c
back to the line that invoked the function. Here is a sample of how the celsius()
function might be invoked:
user_temp_f = float(input("Please enter the temperature in Fahrenheit: "))
user_temp_c = celsius(user_temp_f)
print(f"{user_temp_f} degrees Fahrenheit is {user_temp_c:.1f} degrees Celsius")
The first line gets a float
Fahrenheit temperature from the user.
The second line calls (invokes) the celsius()
function. The value of the variable user_temp_f
is passed as the parameter to the celsius()
function. Within the celsius()
function, that value is stored in the temp_f
variable and the result is computed. Then the celsius()
function returns the float
value result. Back in the main code block, the return value from celsius()
is stored in the user_temp_c
variable.
The last line gives full formatted output to the user.
Note that celsius(user_temp_f)
is actually an expression, with a value (the return value) and a type (float
). This means we can put celsius(user_temp_f)
in our code anywhere that we can validly put any expression. For example:
print(celsius(user_temp_f))
In the above line of code, celsius(user_temp_f)
is an expression whose value is whatever the celsius
function returns. That value is passed in turn as a parameter to the print()
function! This might remind you of function composition in algebra. A more sophisticated example of this is shown below, a compression of the previous example from three lines of code into two:
user_temp_f = float(input("Please enter the temperature in Fahrenheit: "))
print(f"{user_temp_f} degrees Fahrenheit is {celsius(user_temp_f):.1f} degrees Celsius")
Boolean functions
Note that a function may return a value of type bool
. These are called boolean functions, and just as before, the return value can be used as an expression. For example:
# Return True if the first digit of the number is even,
# otherwise return False
def first_digit_even(number: int) -> bool:
# Keep dividing by 10 until the number is less than 10
while number >= 10:
number = number // 10
# Now we have a one-digit number. See if that digit is even
return number%2 == 0
The while
-loop repeatedly divides by 10 using integer division (shifting the number to the right), until only one digit remains. That digit is the first digit of the original number. Then on the last line, the expression number%2==0
evaluates to True
if that first digit is even, and False
if that first digit is odd. The True
or False
value gets returned.
Here's an example of a code snippet that uses the function:
user_num = int(input("Enter an integer: "))
if first_digit_even(user_num):
print(f"The first digit of {user_num} is even.")
else:
print(f"The first digit of {user_num} is odd.")
Putting it all together
The following example code defines and uses a boolean function is_prime()
that determines whether or not an integer is prime.
A prime number is an integer bigger than 1
that is divisible only by 1
and itself.
def is_prime(number: int) -> bool:
"""
A function that determines whether or not a number is prime
parameters:
number: a positive integer greater than 1 (type: int)
return:
True if number is prime, False otherwise (type: bool)
"""
# tester is a possible factor. Start at 2 and we will increase it
tester = 2
while tester < number:
if number % tester == 0:
# We now know that tester divides number, so
# we know the answer - number is not prime!
# The function teriminates, returning False
return False
else:
tester += 1
# We only reach this line if no return False above every occurred.
# This means that the number has no factors and thus it is prime
return True
def main():
# The user inputs an integer and we determine if it is prime
num = int(input("Enter an integer to test for primality: "))
if is_prime(num):
print(f"{num} is prime")
else:
print(f"{num} is not prime")
# Run the program:
if __name__ == '__main__':
main()
Key points:
- The input for this function is an integer bigger than 1 (our program would be improved if we checked for this condition).
- The output is a boolean (
True
ifnumber
is prime,False
otherwise). - It's helpful for boolean functions to have names that reflect that they return a
True/False
result. Here,is_prime()
reads like a question that has a yes/no answer. When we call the function, the lineif is_prime(23):
makes sense when we read it. - A function should only perform one task. Here, the job is to determine whether or not the number is prime. The function does not output the result to the console. The function just returns the result to the line that invoked it. It's up to the caller to interact with the user. This philosophy makes for more flexible and reusable functions.
- Within the loop of the
is_prime()
function, we can only returnFalse
, neverTrue
. Because only by completing the entire loop can we know thatnumber
has no factors (other than1
and itself). Thereturn True
can thus only happen after the loop has completed. - The program could be made to run more efficiently by stopping the loop when
tester
reaches the square root ofnumber
.
By placing the code to determine whether a number is prime into a separate function, we can now re-use it in multiple ways. For example, here is a new main()
function that outputs all primes less than 1000
:
def main():
# Output all primes less than 1000, testing each number one by one
for i in range(2, 1000):
if is_prime(i):
# output the number if it is prime:
print(i, end = " ")
# Move output to a new line
print()
You should notice how simple it was to create this new program. The is_prime()
function did not need to be rewritten or even thought about.
Here is yet another example. This time we will build another layer on top of the is_prime()
function by creating a function called next_prime()
to give the next prime larger than a specific integer. Notice that next_prime()
itself uses is_prime()
, so its job is made straightforward and easy to understand. By the use of these two functions, the interface with the user in main()
is also very straightforward. This organization and structure makes code easier to read and understand, easier to fix if it has an error, and easier to enhance or modify if we choose to later. Notice that the contents of is_prime()
are not shown here, since that function is identical to before.
def is_prime(number: int) -> bool:
# contents not shown - it's unchanged from before!
def next_prime(number: int) -> int:
"""
Return the first prime greater than or equal to number
parameters:
number: input value for which we need to return the next prime (type: int)
return:
a prime value greater than or equal to number (type: int)
"""
# start from number, and increment until we find the next prime
while not is_prime(number):
number += 1
# The loop has terminated, so the current value of number must be the next prime.
return number
def main():
n = int(input("Give me a number and I will tell you the next prime: "))
print(f"The next prime is {next_prime(n)}")
# Run the program:
if __name__ == '__main__':
main()