Skip to main content

Functions

In this chapter you'll learn to write functions, which are named blocks of code that are designed to do one specific job. When you want to perform a particular task that you've defined in a function, you call the name of the function responsible for it.

Defining a Function

Here's a simple function named greet_user():

def greet_user():
"""Display a simple greeting."""
print("Hello!")

greet_user()

Output:

Hello!

Information to a Function

The function greet_user() can be modified to accept a parameter:

def greet_user(username):
"""Display a simple greeting."""
print(f"Hello, {username.title()}!")

greet_user('jesse')

Output:

Hello, Jesse!
Parameters vs Arguments
  • A parameter is a variable used in a function definition (like username)
  • An argument is a value that's passed to a function when it's called (like 'jesse')

Passing Arguments

Because a function definition can have multiple parameters, a function call may need multiple arguments.

Positional Arguments

def describe_pet(animal_type, pet_name):
"""Display information about a pet."""
print(f"\nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")

describe_pet('hamster', 'harry')
describe_pet('dog', 'willie')

Output:

I have a hamster.
My hamster's name is Harry.

I have a dog.
My dog's name is Willie.

Order Matters in Positional Arguments

describe_pet('harry', 'hamster')

Output:

I have a harry.
My harry's name is Hamster.

Keyword Arguments

A keyword argument is a name-value pair that you pass to a function:

def describe_pet(animal_type, pet_name):
"""Display information about a pet."""
print(f"\nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")

describe_pet(animal_type='hamster', pet_name='harry')
describe_pet(pet_name='harry', animal_type='hamster') # Order doesn't matter

Default Values

You can define a default value for each parameter:

def describe_pet(pet_name, animal_type='dog'):
"""Display information about a pet."""
print(f"\nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")

# A dog named Willie
describe_pet(pet_name='willie')
describe_pet('willie')

# A hamster named Harry
describe_pet(pet_name='harry', animal_type='hamster')
describe_pet('harry', 'hamster')
describe_pet(animal_type='hamster', pet_name='harry')
Default Value Order

When you use default values, any parameter with a default value needs to be listed after all the parameters that don't have default values.

# This will cause a SyntaxError
def describe_pet(animal_type='dog', pet_name): # Wrong!
pass

Avoiding Argument Errors

def describe_pet(pet_name, animal_type='dog'):
"""Display information about a pet."""
print(f"\nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")

describe_pet() # TypeError: missing 1 required positional argument

Return Values

A function doesn't always have to display its output directly. Instead, it can process data and then return a value or set of values.

Returning a Simple Value

def get_formatted_name(first_name, last_name):
"""Return a full name, neatly formatted."""
full_name = f"{first_name} {last_name}"
return full_name.title()

musician = get_formatted_name('jimi', 'hendrix')
print(musician)

Output:

Jimi Hendrix

Making an Argument Optional

def get_formatted_name(first_name, last_name, middle_name=''):
"""Return a full name, neatly formatted."""
if middle_name:
full_name = f"{first_name} {middle_name} {last_name}"
else:
full_name = f"{first_name} {last_name}"
return full_name.title()

musician = get_formatted_name('jimi', 'hendrix')
print(musician)

musician = get_formatted_name('john', 'hooker', 'lee')
print(musician)

Output:

Jimi Hendrix
John Lee Hooker

Returning a Dictionary

def build_person(first_name, last_name):
"""Return a dictionary of information about a person."""
person = {'first': first_name, 'last': last_name}
return person

musician = build_person('jimi', 'hendrix')
print(musician)

Output:

{'first': 'jimi', 'last': 'hendrix'}

You can easily extend this function to accept optional values:

def build_person(first_name, last_name, age=None):
"""Return a dictionary of information about a person."""
person = {'first': first_name, 'last': last_name}
if age:
person['age'] = age
return person

musician = build_person('jimi', 'hendrix', age=27)
print(musician)

Output:

{'first': 'jimi', 'last': 'hendrix', 'age': 27}

Using a Function with a while Loop

def get_formatted_name(first_name, last_name):
"""Return a full name, neatly formatted."""
full_name = f"{first_name} {last_name}"
return full_name.title()

while True:
print("\nPlease tell me your name:")
print("(enter 'q' at any time to quit)")

f_name = input("First name: ")
if f_name == 'q':
break

l_name = input("Last name: ")
if l_name == 'q':
break

formatted_name = get_formatted_name(f_name, l_name)
print(f"\nHello, {formatted_name}!")

Passing a List

You'll often find it useful to pass a list to a function:

def greet_users(names):
"""Print a simple greeting to each user in the list."""
for name in names:
msg = f"Hello, {name.title()}!"
print(msg)

usernames = ['hannah', 'ty', 'margot']
greet_users(usernames)

Output:

Hello, Hannah!
Hello, Ty!
Hello, Margot!

Modifying a List in a Function

def print_models(unprinted_designs, completed_models):
"""
Simulate printing each design, until none are left.
Move each design to completed_models after printing.
"""
while unprinted_designs:
current_design = unprinted_designs.pop()
print(f"Printing model: {current_design}")
completed_models.append(current_design)

def show_completed_models(completed_models):
"""Show all the models that were printed."""
print("\nThe following models have been printed:")
for completed_model in completed_models:
print(completed_model)

unprinted_designs = ['phone case', 'robot pendant', 'dodecahedron']
completed_models = []

print_models(unprinted_designs, completed_models)
show_completed_models(completed_models)

Preventing a Function from Modifying a List

Sometimes you'll want to prevent a function from modifying a list. You can send a copy of a list to a function like this:

print_models(unprinted_designs[:], completed_models)

The slice notation [:] makes a copy of the list to send to the function.

Passing an Arbitrary Number of Arguments

Sometimes you won't know ahead of time how many arguments a function needs to accept.

def make_pizza(*toppings):
"""Summarize the pizza we are about to make."""
print("\nMaking a pizza with the following toppings:")
for topping in toppings:
print(f"- {topping}")

make_pizza('pepperoni')
make_pizza('mushrooms', 'green peppers', 'extra cheese')

Output:

Making a pizza with the following toppings:
- pepperoni

Making a pizza with the following toppings:
- mushrooms
- green peppers
- extra cheese

The asterisk in the parameter name *toppings tells Python to make an empty tuple called toppings and pack whatever values it receives into this tuple.

Mixing Positional and Arbitrary Arguments

def make_pizza(size, *toppings):
"""Summarize the pizza we are about to make."""
print(f"\nMaking a {size}-inch pizza with the following toppings:")
for topping in toppings:
print(f"- {topping}")

make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')

Using Arbitrary Keyword Arguments

Sometimes you'll want to accept an arbitrary number of arguments, but you won't know ahead of time what kind of information will be passed to the function:

def build_profile(first, last, **user_info):
"""Build a dictionary containing everything we know about a user."""
user_info['first_name'] = first
user_info['last_name'] = last
return user_info

user_profile = build_profile('albert', 'einstein',
location='princeton',
field='physics')
print(user_profile)

Output:

{'location': 'princeton', 'field': 'physics', 'first_name': 'albert', 'last_name': 'einstein'}

The double asterisks before the parameter **user_info cause Python to create an empty dictionary called user_info and pack whatever name-value pairs it receives into this dictionary.

Storing Functions in Modules

One advantage of functions is the way they separate blocks of code from your main program. You can go a step further by storing your functions in a separate file called a module and then importing that module into your main program.

Importing an Entire Module

Create a file called pizza.py:

# pizza.py
def make_pizza(size, *toppings):
"""Summarize the pizza we are about to make."""
print(f"\nMaking a {size}-inch pizza with the following toppings:")
for topping in toppings:
print(f"- {topping}")

Now create another file in the same directory called making_pizzas.py:

# making_pizzas.py
import pizza

pizza.make_pizza(16, 'pepperoni')
pizza.make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')

Importing Specific Functions

from pizza import make_pizza

make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')

Using as to Give a Function an Alias

from pizza import make_pizza as mp

mp(16, 'pepperoni')
mp(12, 'mushrooms', 'green peppers', 'extra cheese')

Using as to Give a Module an Alias

import pizza as p

p.make_pizza(16, 'pepperoni')
p.make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')

Importing All Functions in a Module

from pizza import *

make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')
warning

It's best to avoid this approach when you're working on projects of your own. Python may see several functions or variables with the same name, and instead of importing all the functions separately, it will overwrite the functions.

Styling Functions

Functions should have descriptive names, and these names should use lowercase letters and underscores. Descriptive names help you and others understand what your code is trying to do.

Every function should have a comment that explains concisely what the function does. This comment should appear immediately after the function definition and use the docstring format.

Summary

In this chapter you learned how to:

  • Define functions using the def statement
  • Pass information to functions using parameters and arguments
  • Use positional arguments, keyword arguments, and default values
  • Return values from functions
  • Use functions with lists and dictionaries
  • Accept a variable number of arguments using *args and **kwargs
  • Store functions in modules and import them
  • Style your functions according to Python conventions

Functions are essential for writing clean, organized, and reusable code. As your programs become more complex, functions will help you organize your code and make it easier to maintain and debug.