You may be familiar with functions from math. The simplest definition of a function is something that maps a given input to a particular output. Functions in computer science can be thought of in mostly the same way. Unfortunately, in practice, things are a bit hairier.
We may not realize it, since we often treat basic arithmetic functions as operations, but $\times$ is technically a function mathematically: it maps inputs (two numbers) to an output (another number). Of course, in Python, * is a special operator and not technically a function, but we can write a function for it.
def multiply(a,b):
"""
Produces the product of the two given numbers.
Input:
a (float): a real number
b (float): a real number
Output: the product a * b (float)
"""
n = a * b
return n
Let's go through the parts of a function definition. First, here is a template for how to define a function.
def function_name(parameter_1, parameter_2, ...):
"""
Description of the function.
Inputs:
List of inputs and their descriptions and types
Output: description of the output (and its type)
"""
<statements>
return <expression>
You'll likely have seen this from completing your programming assignments because we have been having you put your solutions in functions that we have set up.
def followed by the name of the function. The name of the function is how we refer to it.
return statement. This value can be any expression (i.e. literal, variable name, or expression).
Once we have defined a function, we can call it like so:
function_name(a1, a2, ...)
multiply(6,7)
This looks very similar to how we would write $f(x,y)$ in math.
What happens when a function is called? Recall our discussions about expressions: whenever a statement with an expression is executed, the expression will be evaluated. In the same way, for any statement that includes a function call, the function will be evaluated. This means that the control flow will change to evaluate the function in the exeuction of the statement.
Here, we add print statements that will leave a trail of where the flow of control is. Also observe that they do not affect the result of the function—only the value specified by return is considered the output of the function.
def multiply(a,b):
"""
Produces the product of the two given numbers.
Input:
a (float): a real number
b (float): a real number
Output: the product a * b (float)
"""
print(" Start of multiply(a,b) function")
n = a * b
print(" End of multiply(a,b) function")
return n
x = 5
y = 4
print("Calling multiply(x,y)...")
z = multiply(x, y)
print("Returned from multiply(x,y)...")
print("Value of z is", z)
So what are functions in the context of programs? Once our programs start getting to a certain level of complexity, it becomes difficult to think of programs as just a sequence of statements all the time. Just as we did with variables, we can abstract functions and give names, which allows us to reuse them. So while variables allows us to abstract and organize pieces of data, functions can be thought of as an abstraction and organization of computation—in order to do anything interesting, we have to apply some sort of function. In this sense, functions act as a basic unit of computation and we can think of functions as small programs.
This means that we want to put a bit more care in how we define functions. To do this, we'll walk through some steps that will help to guide your problem solving process and writing a complete, documented function.
We'll go through this process with the intent of writing a function to determine whether a given integer is a prime number. (Recall that we've already seen the code for this)
First, we should imagine some examples of what we want our function to do. Here, you might wonder why we're not thinking about the definition of the problem or the function, but coming up with examples is a good way of making your understanding of the problem concrete.
For instance, let's recall the idea of a prime number: it's a number that's divisible only by one and itself. So we know some small primes, like 2, 3, and 5. We also know that any even number is not prime, like 4, 6, and so on. But maybe we should try looking for larger numbers. And what about 1?
Doing enough of this should give us an idea of some patterns we might wish to exploit in order to solve the problem.
So what should our inputs and outputs be? While Python doesn't require us to explicitly include this, it's still very important to think about this. Since Python doesn't check or enforce any type restrictions, this means it is up to the programmers working with the function to ensure it is being used correctly. This is why we'll also be including this information in our documentation of the function.
To see why thinking about types is important, we can go back to math. While we may not think of math as having types, we recall that we talk about functions in the following way: \[f : \mathbb R \times \mathbb R \to \mathbb R\] This is kind of like information about types. This says that our function $f$ takes two real numbers and produces a real number. We say that $\mathbb R \times \mathbb R$ is the domain of $f$ and $\mathbb R$ is the co-domain or range is of $f$.
If we think about what our prime number checking function should do, taking integers as inputs seems to be the correct choice. And since our function is essentially answering a yes or no question, the output of our function should be a boolean. This means we can envision a function that looks like \[f : \mathbb Z \to \{\text{True}, \text{False}\}.\]
Once we're satisfied with this, we can name our function and its parameters. We should give nice descriptive names for things whenever we expect to use them in many contexts. A good name for this particular function is is_prime. But what about the input parameter? That's not quite as important, since the varaible will only live inside the function—we'll talk a bit more about this in detail later.
def is_prime(n):
At this point, we should have a relatively clear idea of what we'd like our function to do, after having thought through examples and what its inputs and outputs should be. Here, we want to write a description of what our function does and include it with the definition of our function.
You might wonder what the point of this is. After all, the code describes the solution to the problem. And this is true, the program is indeed a description of the solution to the problem you're solving. The issue is that while the code will explain literally what it does, it is not very helpful for determining whether the code is doing what it was intended to do.
In other words, we need to document intent. We often forget that our programs are not only intended for the computer. If the program describes a solution, then it's natural that other people will be interested in it. And so we are not communciating only with the computer, but with other people as well. Even in this course, it may seem like no one else is reading your programs, but at the very least, the graders will be. And there is also the reader we never expect to get frustrated: ourselves in a couple of weeks.
What would a possible description of our function look like? This function is fairly straightforward: it figures out whether a number is prime or not. So we might write something like
Determines whether a number is prime.
But we can be a bit more specific. We know a few things already, like what our inputs and outputs should be.
Determines whether a given integer $n$ is prime.
But wait, what's a prime number? If your audience knows what a prime is, that's fine. But if not, it's worth describing it a bit more.
Determines whether a given integer $n$ is prime. A prime number is an integer greater than 1 that is divisible only by 1 and itself.
We will include our plain-English description of the function in the code. Every programming language has a facility for including notes intended for humans to read that are ignored by computers. These are called comments.
In Python, there are two ways to do this. Ordinary comments begin with #.
great_value = 3193 # this value is very great
Anything that follows a hash is ignored. This is useful for leaving comments about points of interest in your code.
However, a second type of comment exists in Python specifically for documenting functions. These are called docstrings and are denoted by three quotes, """. The docstring should include your plain language description of the function as well as its expected inputs and output.
"""
Determines whether the given integer is a prime number. A prime number
is an integer n greater than 1 that has only 1 and n as its divisors.
Input:
n (int): the integer to be tested
Output: True if n is a prime number and False otherwise (bool)
"""
The docstring should go immediately beneath the function header, indented, and before the body of the function definition.
def is_prime(n):
"""
Determines whether the given integer is a prime number. A prime
number is an integer n greater than 1 that has only 1 and n as its
divisors.
Input:
n (int): the integer to be tested
Output: True if n is a prime number and False otherwise (bool)
"""
This is actually just the body of the function, which consists of the expression(s) you wrote. The real work of this part is solving the problem and writing the code for it. But notice how the groundwork that we did in the previous steps clarifies what a possible solution in code might look like—it's important (and helpful!) to think through the problem carefully before sitting down at a keyboard.
We have the definition for is_prime ready to go from our earlier example.
def is_prime(n):
"""
Determines whether the given integer is a prime number. A prime
number is an integer n greater than 1 that has only 1 and n as its
divisors.
Input:
n (int): the integer to be tested
Output: True if n is a prime number and False otherwise (bool)
"""
rv = True # rv is the "return value"
for i in range(2,n):
if n % i == 0:
rv = False
break
return rv
The final piece of function design is testing. Why do we need tests? A common question that many beginners have is "How do I know my code is correct?" In fact, this is a tricky problem that even seasoned engineers will struggle with.
As a theoretician, my answer would be that it's simple, just provide a mathematical proof! Surprisingly, it turns out most people do not want to do this. The next best thing is to provide tests as evidence that your program works.
While there are formal testing facilities (which we make use of to grade your work), it is also a good idea to do informal testing to ensure that your work is correct. In a way, this is a callback to thinking through the problem with examples—this should have given you a good idea of what values to use in your testing and what the expected output should be.
Here, we'll see that we have an issue if we call is_prime(1). This is the source of a bit of controversy, but despite what many people may think or wish, 1 is not a prime number. Luckily, we caught this error and can fix this.
def is_prime(n):
"""
Determines whether the given integer is a prime number. A prime
number is an integer n greater than 1 that has only 1 and n as its
divisors.
Input:
n (int): the integer to be tested
Output: True if n is a prime number and False otherwise (bool)
"""
rv = True # rv is the "return value"
if n == 1:
rv = False
else:
for i in range(2,n):
if n % i == 0:
rv = False
break
return rv