Introduction to functions

Published

2023-07-31

Introduction to functions

Among the most powerful tools we have as programmers—perhaps the most powerful tools—are functions.

We’ve already seen some built-in Python functions, for example, print() and type(). We’ll see many others soon.

Essentially, a function is a sub-program which we can “call” or “invoke” from within our larger program. For example, consider this code which prints a string to the console.

print('Do you want a cookie?')

Here we’re making use of built-in Python function print(). The developers of Python have written this function for you, so you can use it within your program. When we use a function, we say we are “calling” or “invoking” the function.

In the example above, we call the print() function, supplying the string 'Do you want a cookie?' as an argument.

As a programmer using this function, you don’t need to worry about what goes on “under the hood” (which is quite a bit, actually). How convenient!

When we call a function, the flow of control within our program passes to the function, the function does its work, and then returns a value. All Python functions return a value, though in some cases, the value returned is None.1

Defining a function

Python allows us to define our own functions.2 A function is a unit of code which performs some calculation or some task. A function may take zero or more arguments (inputs to the function). The definition of a function may include zero or more formal parameters which are, essentially, variables that will take on the values of the arguments provided. When called, the body of the function is executed. In most, but not all cases, a value is explicitly returned. Returned values might be the result of a calculation or some status indicator—this will vary depending on the purpose of the function. If a value is not explicitly returned, the value None is returned implicitly.

Let’s take the simple example of a function which squares a number. In your mathematics class you might write

f(x) = x^2

and you would understand that when we apply the function f to some argument x the result is x^2. For example, f(3) = 9. Let’s write a function in Python which squares the argument supplied:

def square(x):
    return x * x

def is a Python keyword, short for “define”, which tells Python we’re defining a function. (Keywords are reserved words that are part of the syntax of the language. def is one such keyword, and we will see others soon.) Functions defined with def must have names (a.k.a., “identifiers”),3 so we give our function the name “square”.

Now, in order to calculate the square of something we need to know what that something is. That’s where the x comes in. We refer to this as a formal parameter of the function. When we use this function elsewhere in our code we must supply a value for x. Values passed to a function are called arguments (however, in casual usage it’s not uncommon to hear people use “parameter” and “argument” interchangeably).

At the end of the first line of our definition we add a colon. What follows after the colon is referred to as the body of the function. It is within the body of the function that the actual work is done. The body of a function must be indented as shown below—this is required by the syntax of the language. It is important to note that the body of the function is only executed when the function is called, not when it is defined.

In this example, we calculate the square, x * x, and we return the result. return is a Python keyword, which does exactly that: it returns some value from a function.

Let’s try this in the Python shell to see how it works:

>>> def square(x):
...     return x * x
...
>>>

Here we’ve defined the function square(). Notice that if we enter this in the shell, after we hit return after the colon, Python replies with ... and indents for us. This is to indicate that Python expects the body of the function to follow. Remember: The body of a function must be indented. Indentation in Python is syntactically significant (which might seem strange if you’ve coded in Java, C, C++, Rust, JavaScript, C#, etc.; Python uses indentation rather than braces).

So we write the body—in this case, it’s just a single line. Again, Python replies with ..., essentially asking “Is there more?”. Here we hit the return/enter key, and Python understands we’re done, and we wind up back at the >>> prompt.

Now let’s use our function by calling it. To call a function, we give the name, and we supply the required argument(s).

>>> square(5)   # call `square` with argument 5 
25
>>> square(7)   # call `square` with argument 7
49
>>> square(10)  # call `square` with argument 10
100

Notice that once we define our function we can reuse it over and over again. This is one of the primary motivations for functions.

Notice also that in this case, there is no x outside the body of the function.

>>> x
Traceback (most recent call last):
  File "/blah/blah/code.py", line 90, in runcode
    exec(code, self.locals)
  File "<input>", line 1, in <module>
NameError: name 'x' is not defined

In this case, x exists only within the body of the function.4 The argument we supply within the parentheses becomes available within the body of the function as x. The function then calculates x * x and returns the value which is the result of this calculation.

If we wish to use the value returned by our function we can save it by assigning the value to some variable, or use it in an expression, or even include it as an argument to another function!

Can we create new variables in our functions?

Yes. Of course.

def cube(x):
    y = x ** 3   # assign result local variable `y`
    return y     # return the value of `y`

We refer to such variable names (y in this example) as local variables, and like the formal parameters of a function, they exist only within the body of the function.

Storing a value returned by a function

Continuing with our example of square():

>>> a = 17
>>> b = square(a)
>>> b
289

Notice that we can supply a variable as an argument to our function. Notice also that this object needn’t be called x.5

Using the value returned by a function in an expression

Sometimes there’s no need for assigning the value returned by a function to a variable. Let’s use the value returned by the square() function to calculate the circumference of a circle of radius r.

>>> PI = 3.1415926
>>> r = 126.1
>>> PI * square(r)
49955.123667046

Notice we didn’t assign the value returned to a variable first, but rather, we used the result directly in an expression.

Passing the value returned from a function to another function

Similarly, we can pass the value returned from a function to another function.

>>> print(square(12))
144

What happens here? We pass the value 12 to the square() function, this calculates the square and returns the result (144). This result becomes the value we pass to the print() function, and, unsurprisingly, Python prints 144.

Do all Python functions return a value?

This is a reasonable question to ask, and the answer is “yes.”

But what about print()? Well, the point of print() is not to return some value but rather to display something in the console. We call things that a function does apart from returning a value side effects. The side effect of calling print() is that it displays something in the console.

>>> print('My hovercraft is full of eels!')
My hovercraft is full of eels!

But does print() return a value? How would you find out? Can you think of a way you might check this?

What do you think would happen here?

>>> mystery = print('Hello')

Let’s see:

>>> mystery = print('Hello')
Hello
>>> print(mystery)
None

None is Python’s special way of saying “no value.” None is the default value returned by functions which don’t otherwise return a value. All Python functions return a value, though in some cases that value is None.6 So print() returns the None.

How do we return None (assuming that’s something we want to do)? By default, in the absence of any return statement, None will be returned implicitly.

>>> def nothing():
...     pass  # `pass` means "don't do anything"
... 
>>> type(nothing())
<class 'NoneType'>

Using the keyword return without any value will also return None.

>>> def nothing():
...     return
... 
>>> type(nothing())
<class 'NoneType'>

Or, if you wish, you can explicitly return None.

>>> def nothing():
...     return None
... 
>>> type(nothing())
<class 'NoneType'>

What functions can do for us

  • Functions allow us to break a program into smaller, more manageable pieces.
  • They make the program easier to debug.
  • They make it easier for programmers to work in teams.
  • Functions can be efficiently tested.
  • Functions can be written once, and used many times.

Comprehension check

  1. Write a function which calculates the successor of any integer. That is, given some argument n the function should return n + 1.

  2. What’s the difference between a formal parameter and an argument?

  3. When is the body of a function executed?

Original author: Clayton Cafiero < [given name] DOT [surname] AT uvm DOT edu >

No generative AI was used in producing this material. This was written the old-fashioned way.

This material is for free use under either the GNU Free Documentation License or the Creative Commons Attribution-ShareAlike 3.0 United States License (take your pick).

Footnotes

  1. Unlike C or Java, there is no such thing as a void function in Python. All Python functions return a value, even if that value is None.↩︎

  2. Different languages have different names for sub-programs we can call within a larger program, for example, functions, methods, procedures, subroutines, etc., and some of these designations vary with context. Also, these are defined and implemented somewhat differently in different languages. However, the fundamental idea is similar for all: these are portions of code we can call or invoke within our programs.↩︎

  3. Python does allow for anonymous functions, called “lambdas”, but that’s for another day. For the time being, we’ll be defining and calling functions as demonstrated here.↩︎

  4. This is what is called “scope”, and in the example given x exists only within the scope of the function. It does not exist outside the function—for this we say “x is out of scope.” We’ll learn more about scope later.↩︎

  5. In fact, even though it’s syntactically valid for a variable in the outer scope to have the same name as a parameter to a function, or a local variable within a function, it’s best if they don’t have the same identifier. See the section on “shadowing” for more.↩︎

  6. If you’ve seen void in C++ or Java you have some prior experience with functions that don’t return anything. All Python functions return a value, even if that value is None.↩︎