Dr Seán Carroll

by Dr Seán Carroll

Lesson

Python 101 - The (very) Basics

13. Functions in Python

In this lesson, we’ll introduce functions. You can think of a function as a box that takes in some input, does something with it, and then returns some output. As you start to write more complex and elaborate code, you begin to make use of functions more and more.

They allow you to break your code into smaller, more manageable chunks. They also allow you to reuse code. If you find yourself writing the same code over and over again, you can put it in a function and call the function instead, usually with just a single line of code.

We’ll start with a simple example. Let’s say we want to write a function that takes in a number and returns that number plus 1. We’ll call this function add_one. We can define it like this:

def add_one(x):
  y= x + 1
  return y

The first line is the function definition. It starts with the keyword def, followed by the name of the function. We can name the function anything we like but it makes sense to give it a descriptive name. The name is followed by the input to the function in parentheses. If the function doesn’t take any inputs, we can leave the parentheses empty.

The input is followed by a colon. The second line is the body of the function. It is indented by one tab indentation and contains the code that will be executed when the function is called.

The third line is the return statement. It returns the value of the expression after the return statement. In this case, it returns the value stored in the variable y which we assigned x + 1 to.

At this point, all we’ve done is define the function. Next, we need to call it like this:

output = add_one(5)
print(output)

The output is 6, as expected. We can also call the function with a variable as input:

y = 10
print(add_one(y))

The output is 11, as expected. We can call the function with an expression as input:

print(add_one(2 + 3))

And we can call the function with a function call as input:

print(add_one(add_one(5)))

Default values and keyword arguments


When defining a function, we can specify default values for its parameters. So, when the function is called, if an argument is not provided for a parameter with a default value, the default value is used.

Here's a quick example of a function that takes a base number and raises it to the power of an exponent. The exponent has a default value of 2:

def power(base, exponent=2):
    return base ** exponent #Note how we use ** instead of ^

result = power(3)  # Exponent defaults to 2
print(result)  # Output: 9

Alternatively we can call the function with a value for the exponent parameter that will override the default:

print(power(3, 3))  # Exponent is now 3, output is 27

One issue with the way we’ve defined our function is that we have to remember the order of the parameters. If we pass the arguments in the wrong order, our function will give us what we perceive to be the wrong answer. For example, consider the effect of swapping two different argument values:

print(f"The result of 3 to the power of 4 is {power(3, 4)}")
# Output is 81

print(f"The result of 4 to the power of 3 is {power(4, 3)}")
# Output is 64

We can avoid this ambiguity by using keyword arguments. When we call the function, we can specify the name of the parameter and its value:

result = power(exponent=3, base=4)
print(result) # Output is 64

As we write longer functions, it can be useful to include a docstring. A docstring is a string that describes what the function does. It’s placed at the top of the function body and is enclosed in triple quotes. I recommend making use of docstrings for most functions - your future self will thank you when you return to your code weeks or months down the line. Here’s a simple example:

def multiply(a, b):
    """
    Multiply two numbers and return the result.

    Parameters:
    a (int): The first number.
    b (int): The second number.

    Returns:
    int: The product of a and b.
    """
    return a * b

Exercise

There are a few more details that we could explore with functions but there's no sense in getting bogged down with those now. Instead, try writing a simple function to calculate various properties of a sphere, given some radius rr. The function should return the diameter, volume and surface area of the sphere. The volume and surface area are given by the following expressions: volume = 43πr3\frac{4}{3}\pi r^3, surface area = 4πr24\pi r^2. Return the values in the form of a sentence. For example, "The diameter of the sphere is 10.0..."

Solution

We can start by defining the function and its input parameter. We use a docstring to describe what the function does. We'll also import the value of π\pi from the math module. We'll use the math.pi notation to access the value of π\pi.

import math # We need the math module to get the value of pi

def sphereProperties(radius):
    """
    Calculate the properties of a sphere.

    Parameters:
    radius (float): The radius of the sphere.

    Returns:
    tuple: The diameter, volume and surface area of the sphere.
    """

    diameter = 2 * radius
    volume = (4 / 3) * math.pi * radius ** 3
    area = 4 * math.pi * radius * radius

    return diameter, volume, area

Now let's call the function with a radius of 2 and directly print the output.

print(sphereProperties(2))
# Output (4, 33.510321638291124, 50.26548245743669)

Note that the values are returned in a tuple (indicated by the rounded brackets) - recall from the previous lesson that this is one of our collection data types. We can access the individual values in the tuple using indexing:

diameter = sphereProperties(2)[0]
volume = sphereProperties(2)[1]
surface_area = sphereProperties(2)[2]

Now we can construct a sentence using an f-string to insert the variables into the sentence:

f"The diameter of the sphere is {diameter}, the volume is {volume} and the surface area is {surface_area}."

This yields the following output:

The diameter of the sphere is 4, the volume is 33.510321638291124 and the surface area is 50.26548245743669.

One final detail we might want to address is the number of decimal places. Note that the radius is an integer but the volume and surface area are floats. This variable typing was handled by Python, behind the scenes. We can confirm the types by calling the type function on each variable and directly printing the output:

print(type(diameter))
print(type(volume))
print(type(surface_area))

This yields the following output:

<class 'int'>
<class 'float'>
<class 'float'>

This output is indicated the type of each variable. The word class is indicating that int and float are actually built-in python classes. We touch on classes a little later - for now, the important thing to note is that the type of each variable is indicated by the word after the word class.

Now let's tidy up the floats by rounding them to 2 decimal places. We can do this using the round() function. The first argument is the number we want to round and the second argument is the number of decimal places we want to round to for example, round(3.141592653589793, 2) returns 3.14. We can use this function to round the volume and surface area to 2 decimal places and reprint the string:

volume = round(volume, 2)
surface_area = round(surface_area, 2)
f"The diameter of the sphere is {diameter}, the volume is {volume} and the surface area is {surface_area}."

This yields the following output:

The diameter of the sphere is 4, the volume is 33.51 and the surface area is 50.27.