Writing Functions

This lecture discusses the mechanics of writing functions and how to encapsulate scripts as functions.


In [1]:
# Example: We're going to use Pandas dataframes to create a gradebook for this course

import pandas as pd

# Student Rosters:
section_1_students = ['Hao', 'Jennifer', 'Alex']
section_2_students = ['Christa', 'Troy', 'Xin']

# Gradebook columns:
columns = ['raw_grade', 'did_extra_credit', 'final_grade']

# Let's create two dataframes, one for each class section
section_1_gradebook = pd.DataFrame(index=section_1_students, columns=columns)
section_2_gradebook = pd.DataFrame(index=section_2_students, columns=columns)

print("Section 1 Gradebook:")
print(section_1_gradebook)

print("\n") # prints a newline character to put an empty line

print("Section 2 Gradebook:")
print(section_2_gradebook)


Section 1 Gradebook:
         raw_grade did_extra_credit final_grade
Hao            NaN              NaN         NaN
Jennifer       NaN              NaN         NaN
Alex           NaN              NaN         NaN


Section 2 Gradebook:
        raw_grade did_extra_credit final_grade
Christa       NaN              NaN         NaN
Troy          NaN              NaN         NaN
Xin           NaN              NaN         NaN

In [2]:
# Now let's add some data
# (in real life we might load this from a CSV or other file)
section_1_gradebook.loc['Hao']['raw_grade'] = 80
section_1_gradebook.loc['Hao']['did_extra_credit'] = True # python supports boolean (True/False) values
section_1_gradebook.loc['Jennifer']['raw_grade'] = 98
section_1_gradebook.loc['Jennifer']['did_extra_credit'] = False
section_1_gradebook.loc['Alex']['raw_grade'] = 85
section_1_gradebook.loc['Alex']['did_extra_credit'] = True

section_2_gradebook.loc['Christa']['raw_grade'] = 91
section_2_gradebook.loc['Christa']['did_extra_credit'] = False
section_2_gradebook.loc['Troy']['raw_grade'] = 89
section_2_gradebook.loc['Troy']['did_extra_credit'] = True
section_2_gradebook.loc['Xin']['raw_grade'] = 89
section_2_gradebook.loc['Xin']['did_extra_credit'] = False
                            
print("Section 1 Gradebook:")
print(section_1_gradebook)

print("\n") # prints a newline character to put an empty line

print("Section 2 Gradebook:")
print(section_2_gradebook)


Section 1 Gradebook:
         raw_grade did_extra_credit final_grade
Hao             80             True         NaN
Jennifer        98            False         NaN
Alex            85             True         NaN


Section 2 Gradebook:
        raw_grade did_extra_credit final_grade
Christa        91            False         NaN
Troy           89             True         NaN
Xin            89            False         NaN

In [ ]:
# Let's give everyone who did Extra Credit 5 additional points
# But we'll put a max of 100 points

# We'll do section 1 first:




# Now let's print out the results:
print("Section 1 Final Grades:")
print(section_1_gradebook)

In [ ]:
# Now let's do Section 2:


        
# Now let's print out the results:        
print("Section 2 Final Grades:")
print(section_2_gradebook)

Copying and pasting code can introduce bugs:

You might forget to change a variable name.

If you later make a change (like making extra credit worth 10 points instead of 5), you need to remember to change it in multiple places.

If we put the code in a function, we can avoid these problems!


In [ ]:
# Let's put our extra credit code in a function!

Why write functions?

  1. Easily reuse code (without introducing bugs)
  2. Easy testing of components
    • Later in the course we will learn about writing unit tests. You will create a set of input values for a function representing potential scenarios, and will test that the function is generating the expected output.
  3. Better readability
    • Functions encapsulate your code into components with meaningful names. You can get a high-level view of what the code is doing, then dive into the function definitions if you need more detail.

A function should have one task

Functions should usually be pretty short.

It's good to think about functions as trying to do one single thing.


In [ ]:
# Let's decompose our other function into two functions that each do one thing


extra_credit_value = 10
add_final_grades_for_section(section_1_gradebook, extra_credit_value)
add_final_grades_for_section(section_2_gradebook, extra_credit_value)

Mechanics of Writing a Function

  • Function definition line - How python knows that this is a function
  • Function body - Code that does the computation of the function
  • Arguments - the values passed to a function
  • Formal parameters - the values accepted by the function (the arguments become the formal parameters once they are inside the function)
  • Return values - value returned to the caller

If you are familiar with other languages like Java, you may have needed to declare the types of the parameters and return value. This is not necessary in Python.


In [ ]:
def example_addition_function(num_1, num_2):
    """
    This function adds two numbers.
    
    example_addition_function is the function name

    Parameters:
        num_1: This is the first formal parameter
        num_2: This is the second formal parameter

    Returns:
        sum of num_1 and num_2
    
    """    
    added_value = num_1 + num_2
    return added_value

arg_1 = 5
arg_2 = 10
result_value = example_addition_function(arg_1, arg_2) # arg_1 and arg_2 are the arguments to the function

Variable names and scope

In Python, variables have a scope (a context in which they are valid).

Variables created in a function cannot be referenced outside of a function


In [ ]:
def print_message(message):
    message_to_print = "Here is your message: " + message
    print(message_to_print)
    
my_message = "Hello, class!"
print_message(my_message)

#print(message_to_print) # this will cause an error. This variable only exists within the function.

If you modify an object (like a list or a dataframe) inside of a function, the modifications will affect its value outside of the function


In [ ]:
def add_name_to_list(name_list, new_name):
    name_list.append(new_name)
    
teachers = ["Bernease", "Dave", "Joe"]
print(teachers)
add_name_to_list(teachers, "Colin")
print(teachers)

Exercise: Write a function to determine if a number is prime

Below is some code that checks if a number is prime. The code has a bug in it!


In [ ]:
# Determine if num is prime
# This code has a bug. What is it?
# Also, the efficiency of the code can be improved. How?

num = 3
is_prime = True

for integer in range(1, num):
    if num % integer == 0: 
        # The "==" operator checks for equality and returns True or False. 
        # Note the difference between "==" and "=", which assigns a value to a variable.
        #
        # The "%" operator calculates the remainder of a division operation
        # if the remainder is zero, integer is a divisor of num, so num is not prime
        print("Not prime!")
        is_prime = False

if is_prime:
    print("Is prime!")

Once you've identified the bug in the above code, take the code and turn it into a function that takes a number as input and returns True if the number is prime and False if it is not.

See if you can find any ways to make the code more efficient.


In [ ]:
# Write a function that takes a number as input and determines if it is prime.
# If it is prime, return True
# Otherwise, return False

Test cases

What are some numbers that we might use as tests to see if our function is working right?

Think of different classes of numbers that might have different behavior, like even vs. odd numbers, and make sure you are including examples from each class. Also try to think of some "edge cases" that might cause strange behavior.

Think of some good test cases for your function and see if you get the results you expect:


In [ ]:
# Check your function on several test cases to make sure you are getting the correct output:

Exercise - Write a Function

  • Write a function that finds two factors of a provided number if it is not prime.
    • What did you name the function?
    • What are the formal arguments? What did you name them?
    • What value(s) are returned?

Crafting a Function

Crafting refers to what you want a function to do.

  • What are the arguments?
  • Should it refer to names outside the function definition?
  • Should it return a value?

Problem: Write a function that finds all of the prime numbers less than a given value.


In [ ]:
# Function header: name, arguments
# Function logic
# return something

Exercise - Craft a Function

Create a function (or set of functions) that finds the prime factors of a number.

  • What did you name the function?
  • What are the arguments?
  • What does the function return?