Python Basics #16 — Functions

3 min read

In this lesson we’ll cover functions.

For convenience we’ll use Jupyter Notebook in this lesson. If you don’t know how to use Notebook, see the previous lesson:

How to use Jupyter Notebook

A function in programming is an object stored in a namespace under a name, used to reuse a block of code. Put another way: a function is a programming construct where you give a name to frequently-used code, store it in memory, and run it later by calling that name. Have you heard the abbreviation D.R.Y.? It stands for “Don’t Repeat Yourself.” Don’t repeat the same actions. Functions reduce code repetition, shorten coding time, and improve readability — making debugging easier.

D.R.Y. stands for “Don’t Repeat Yourself.” It’s an essential concept every programmer should remember.

First, how to define a function:

Jupyter Notebook
# Use the def keyword to define a function.
def hello():  # the function's name is hello.
    # From here to before the return keyword is the "function body".
    """
    This is a docstring — a description of the function.
    """
    print('Hello!')  # prints the string 'Hello!' when the function is called.
    return None  # use return to return a result.

To define a function, use the def keyword. After def, write the function name. After the name, add parentheses and a colon. Everything from the next line down to the end of the function is the function body — the block that runs when the function is called. At the very top of the body you can write a description, called a docstring, which is what the __doc__ method or help function returns:

Jupyter Notebook
# Use the def keyword to define a function.
def hello():  # the function's name is hello.
    # From here to before the return keyword is the "function body".
    """
    This is a docstring — a description of the function.
    """
    print('Hello!')  # prints the string 'Hello!' when the function is called.
    return None  # use return to return a result.


print('[Using __doc__ method]')
print(hello.__doc__)

print('[Using help function]')
help(hello)
Result
[Using __doc__ method]

    This is a docstring — a description of the function.
    
[Using help function]
Help on function hello in module __main__:

hello()
    This is a docstring — a description of the function.

To call a defined function, append parentheses to its name:

Jupyter Notebook
def hello():
    print('Hello!')

hello()
Result
Hello!

Don’t forget the parentheses when calling a function. Without them you’ll get the function object itself, not the result of running the function.

A function defined by you is called a user-defined function. Functions like help that come with Python without you defining them are called built-in functions. Python ships with many useful built-ins — that’s why people say “Python comes with batteries included.”

Functions can have parameters. The variables defined inside the parentheses when defining a function are called parameters; they’re only accessible inside the function:

Jupyter Notebook
def hello(name):  # the variable inside the parentheses is a parameter.
    print('Hello, {}!'.format(name))

# What you pass when calling is the argument.
hello('pink')
Result
Hello pink

If you reference the name parameter outside the function, you get a NameError:

Jupyter Notebook
def hello(name):
    print('Hello, {}!'.format(name))

print(name)
Result
NameError: name 'name' is not defined

What if you define a name variable outside the function and then call the function?

Jupyter Notebook
def hello(name):
    print('Hello, {}!'.format(name))

name = 'sanghee'
hello('pink')
Result
Hello, pink!

pink was printed. Did the outside name variable change? Let’s check:

Jupyter Notebook
def hello(name):
    print('Hello, {}!'.format(name))

name = 'sanghee'
hello('pink')
print(name)
Result
Hello, pink!
sanghee

The outer name is unchanged. Parameters defined inside a function live in that function’s namespace temporarily — even with the same name as an outer variable, they’re a completely separate object in a completely separate space.

Conversely, defining a new variable inside the function with the same name as an outer variable doesn’t affect the outer variable:

Jupyter Notebook
name = 'sanghee'

def hello(name):
    name = 'you are {}'.format(name)
    print('Hello, {}!'.format(name))

hello('pink')

print(name)
Result
Hello, you are pink!
sanghee

Understanding why requires understanding namespaces — we’ll cover that in detail in a separate lesson after the basics series.

When calling a function with a parameter, you must pass an argument. Without it you’ll get a TypeError:

Jupyter Notebook
def hello(name):
    print('Hello, {}!'.format(name))

hello()
Result
TypeError: hello() missing 1 required positional argument: 'name'

To avoid this, you can give the parameter a default value. If no argument is passed, the default is used:

Jupyter Notebook
def hello(name='pink'):
    print('Hello, {}!'.format(name))

hello()
Result
Hello, pink!

If an argument is passed, it overrides the default:

Jupyter Notebook
def hello(name='pink'):
    print('Hello, {}!'.format(name))

hello('sanghee')
Result
Hello, sanghee!

Functions can have multiple parameters. Let’s write a function that takes two numbers and prints their sum:

Jupyter Notebook
def add(x, y):
    print(x + y)

add(1, 1)
Result
2

In practice, the more common purpose of using a function isn’t to print the result — it’s to return the result so you can use it elsewhere. Use the return keyword to return a value:

Jupyter Notebook
def add(x, y):
    return x + y

result = add(1, 1)
print(result)
Result
2

By returning a value, you can pass a function’s return value as the argument to another function:

Jupyter Notebook
def add(x, y):
    return x + y

result = add(add(1, 1), add(1, 1))
print(result)
Result
4

Functions aren’t limited to returning a single value — they can return multiple. Let’s write one that takes a word and returns its first and last characters:

Jupyter Notebook
def first_and_last(word):
    return word[0], word[-1]

print(first_and_last('letter'))
Result
('l', 'r')

When a function returns multiple values, Python packs them into a tuple. You can use unpacking to assign them to two separate variables:

Jupyter Notebook
def first_and_last(word):
    return word[0], word[-1]

first, last = first_and_last('letter')
print('first: {}, last: {}'.format(first, last))
Result
first: l, last: r

Now suppose you have several lists and need to compute the sum of each list’s items.

Jupyter Notebook
list_1 = [1, 2, 3]
list_2 = [4, 5, 6]
list_3 = [7, 8, 9]

Of course you can solve this trivially with the built-in sum:

Jupyter Notebook
list_1 = [1, 2, 3]
list_2 = [4, 5, 6]
list_3 = [7, 8, 9]

print(sum(list_1))
print(sum(list_2))
print(sum(list_3))
Result
6
15
24

But if we pretend the built-in didn’t exist, we might write something like:

Jupyter Notebook
list_1 = [1, 2, 3]
list_2 = [4, 5, 6]
list_3 = [7, 8, 9]

total = 0
for i in list_1:
    total += i
print(total)

total = 0
for i in list_2:
    total += i
print(total)

total = 0
for i in list_3:
    total += i
print(total)
Result
6
15
24

The result is correct, but the same pattern repeats over and over. This violates D.R.Y. and is bad code. Extract the repeated logic into a function:

Jupyter Notebook
list_1 = [1, 2, 3]
list_2 = [4, 5, 6]
list_3 = [7, 8, 9]

def get_total(a_list):
    total = 0
    for i in a_list:
        total += i
    return total

print(get_total(list_1))
print(get_total(list_2))
print(get_total(list_3))
Result
6
15
24

Cleaner. And the next time you need the same operation, you just call get_total. Modifying code to reduce repetition and improve quality like this is called refactoring. Professional developers refactor constantly to make their programs better. Build the habit of reviewing your finished code and extracting repeated parts into functions.

That wraps up today’s lesson. In the next lesson we’ll cover reading and writing files.

X