Chapter 8 – Functions
- functions – named blocks of code designed to do one job
- call – used when you want to perform a particular task that you’ve defined in a function
- call the function to perform the same tasks multiple times
- modules – separate files that help organize your main program files
Defining a function
- greet_user() function – greets users
greeter.py
def greet_user():
….”””Display a simple greeting.”””
….print*”Hello!”)
greet_user()
- def – keyword informs Python that you’re defining a function
- function definition – tells Python the name of the function and, if applicable, what kind of information the function needs to do its job
- parentheses contain information Python would need
- in this case, parentheses are empty because no information is needed to do its job
- definition ends in a colon
- body – any indented lines following the definition of a function
- docstring – text on the second line is a comment that describes what the function does
- Python looks for the string immediately after the uncitno’s definition when gerating documentation fo the functions
- these strings are enclosed in triple quotes, which lets you write multiple lines
- print(“Hello!”) is the only line of code in the body of this function, so greet_user() has one job
- function call – tells Python to execute the code in the function
- call a function by writing the name of the function, followed by any necessary information in parentheses
Passing information to a function
- enter username in the parentheses of the function’s definition at def greet_user() to greet the user by name
- function expects you to provide a value for username each time you call it
- when calling greet_user(), you can pass it a name, such as ‘jesse’, inside the parentheses
def greet_user(username):
….”””Display a simple greeting.”””
….print(f”Hello, {username.title()}!””)
greet_user(‘jesse’)
Hello, Jesse!
Arguments and parameters
- parameter – piece of information the function needs to do its job, such as the variable username in the definition of greet_user()
- value ‘jesse’ in greet_user(‘jesse’) is an example of an argument
- argument – a piece of information that’s passed from a function call to a function
- place value we want to work with in the parentheses
Passing arguments
- since a function definition can have multiple parameters, a function call may need multiple arguments
- can use positional arguments, which need to be in the same order parameters were written or keyword arguments, where each argument consists of a variable name and a value, and lists and dictionaries of values
Positional arguments
- positional arguments – values matched up in the order of arguments provided
pets.py
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’)
- definition shows this function needs a type of animal and the animal’s name
- when calling describe_pet(), we need to provide the info in that order
- argument ‘hamster’ is assigned to the parameter animal_type
- argument ‘harry’ is assigned to the parameter pet_name
I have a hamster.
My hamster’s name is Harry.
Multiple function calls
- can call a function as many times as needed
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’)
I have a hamster.
My hamster’s name is Harry.
I have a dog.
My dog’s name is Willie.
- calling a function multiple times is a very efficient way to work
Order matters in 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(‘harry’, ‘hamster’)
I have a harry.
My harry’s name is Hamster.
- check order of arguments is correct if you get funny results
Keyword arguments
- keyword argument – a name-value pair that you pass to a function
- directly associate the name and the value within the argument
- don’t need to list them in order
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’)
Default values
- default value – Python will use this if an argument for a parameter is not provided in the function call
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(pet_name=’willie’)
- changed definition of describe_pet() to include the default value, ‘dog’, for animal_type
I have a dog.
My dog’s name is Willie.
- order of parameters in the function definition had to change because the default value makes it unnecessary to specify a type of animal as an argument
- the only argument left in the function call is the pet’s name
- still interpreted as a positional argument, so if the function is called with a pet’s name, the argument will match up with the first parameter
Equivalent function calls
- since positional arguments, keyword arguments, and default values can all be used together, there are different ways to call a function
def describe_pet(pet_name, animal_type=’dog’):
- with this definition, an argument always needs to be provided for pet_name, and this value can be provided using the positional or keyword format
- if the animal being described is not a dog, an argument for animal_type must be included, and it can be specified using the positional or keyword format
# A dog named Willie.
describe_pet(‘willie’)
describe_pet(pet_name=’willie’)
# A hamster named Harry.
describe_pet(‘harry’, ‘hamster’)
describe_pet(pet_name=’harry’, animal_type=’hamster’)
describe_pet(animal_type=’hamster’, pet_name=’harry’)
- output is the same, so choose your own style
Avoiding arguments errors
- with functions, unmatched arguments errors are common
- occur when you provide fewer or more arguments than a function needs to do its work
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()
Traceback error.
- missing two arguments
- give variables and functions descript names so error messages can be more useful
Return values
- return value – value the function returns
- allows you to move much of your program’s grunt work into functions
Returning a simple value
formatted_name.py
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)
Jimi Hendrix
- seems like a lot when you could just do print(“Jimi Hendrix”), but this is useful with a large program that needs to store many first and last names separately, functions like get_formatted_name() become very useful
Make an argument optional
- makes sense to make an argument optional so people can provide extra information only if they want to
- for example, expanding get_formatted_name() to handle middle names
def get_formatted_name(first_name, middle_name, last_name):
….”””Return a full name, neatly formatted.”””
….full_name = f”{first_name} {middle_name} {last_name}”
….return full_name.title()
musician = get_formatted_name(‘john’, ‘lee’, ‘hooker’)
print(musician)
John Lee Hooker
- but middle names aren’t always needed
- can make the middle name optional by giving the middle_name argument an empty default value and ignore the argument unless the user provides a value
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)
- since there’s always a first and last name, these parameters are listed first
- middle name is optional, is listed last, and its default value is an empty string
- Python interprets non-empty strings as True, so the conditional test if middle_name evaluate to True if a middle name argument is in the functional call
Jimi Hendrix
John Lee Hooker
Returning a dictionary
- a function can return any kind of value, like complicated data structures such as lists and dictionaries
person.py
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)
- function build_person() takes in a first and last name, and puts these values into a dictionary
- value first_name is stored with the key ‘first’, value last_name is stored with the key ‘last’
- return value is printed with the original two pieces of textual information now stored in a dictionary
{‘first’: ‘jimi’, ‘last’: ‘hendrix’}
- can extend function to include middle name, age, occupation, or other information
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)
Using a function with a while loop
- get_formatted_name() function with a while loop to greet users more formally
greeter.py
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()
# This is an infinite loop!
while True:
….print(“\nPlease tell me your name:”)
….f_name = input(“First name:”)
….l_name = input(“Last name:”)
….formatted_name = get_formatted_name(f_name, l_name)
….print(f”\nHello, {formatted_name}!”)
- problem: we haven’t defined a quit condition for the while loop
- break statement offers a straightforward way to exit the loop at either prompt
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}!”)
- program will continue greeting people until someone enters q for either name:
Please tell me your name:
(enter ‘q’ at any time to quit)
First name: eric
Last name: matthes
Hello, Eric Matthes!
Please tell me your name:
(enter ‘q’ at any time to quit)
First name: q
Passing a list
- it’s useful to pass a list to a function, whether it’s a list of names, numbers, or more complex objects, such as dictionaries
- when passing a list to a function, the function gets direct access to the contents of the list
- functions make working with lists more efficient
- for example, if we have a list of users and want to print a greeting to each user
greet_users.py
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 = [‘john’, ‘ty’, ‘mark’]
greet_users(usernames)
- outside of the function, we define a list of users and then pass the list usernames to greet_users() in the function call
Hello, John!
Hello, Ty!
Hello Mark!
Modifying a list in a function
- when passing a list to a function, the function can modify the list
- changes made to the list inside the function’s body are permanent
- for example, company that creates 3D printed models of designs that users submit
- designs need to be printed and stored in a list, after being printed they are moved to a separate list
- following code does this without using functions
printing_models.py
# Start with some designs that need to be printed.
unprinted_designs = [‘phone case’, ‘robot pendant’, ‘dodecahedron’]
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)
# Display all completed models.
print(“\nThe following models have been printed:”)
for completed_model in completed_models:
….print(completed_model)
- program starts with a list of designs that need to be printed and an empty list called completed_models that each design will be moved to after being printed
- while loop prints each design by removing it from the end of the list and storing it in current_design, before adding it to the list of completed models
Printing model: dodecahedron
Printing model: robot pendant
Printing model: phone case
The following models have been printed:
dodecahedron
robot pendant
phone case
- can reorganize this code by writing two functions, each of which does one specific job
- the first function will handle printing the designs, and the second will summarize the prints that have been made
def print_models(unprinted_designs, completed_models):
….”””
…Simulate printing each design, until none are left.
….Move each design to completed_models after printing.
….”””
….while unprint_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)
- define function print_models() with two parameters: a list of designs that need to be printed and a list of completed models
- print each design by emptying one list and filling up the other
- define show_completed_models() with one parameter: a list of completed models
- easier to follow program that has the same output as the previous version without functions
- this program is easier to extend and maintain, can call print_models() again if we need to print more designs
- can change the code if we realize the printing code needs to be modified
- more efficient than updating code separately in several places
- every function has one specific job, which is more beneficial than using one function to do both jobs
Preventing a function from modifying a list
- can keep the original list intact by modifying a copy of it
function_name(list_name[:])
- slice notation [:] makes a copy of the list to send to the function
print_models(unprinted_designs[:], completed_models)
- this will keep the original list of unprinted designs intact while filling up the completed_models list
- only use when needed, as working with the original list is more efficient
End of study session.