Functions

Functions are key to creating reusable software, testing, and working in teams. This lecture motivates the use of functions, discusses how functions are defined in python, and introduces a workflow that starts with exploratory code and produces a function.

Topics

  • Creating reusable software components
  • Motivating example
  • Python function syntax
  • Name scoping in functions
  • Keyword arguments
  • Exercise
  • Function Driven Workflow

Creating Reusable Software Components

  • What makes a component reusable?
  • Signature of a software component
    • Inputs
      • How they are "passed"
      • Data types
      • Semantics
    • Outputs
    • Side effects

Motivating Example


In [6]:
# Our prime number example from week 1
N = 10
for candidate in range(2, N):
    # n is candidate prime. Check if n is prime
    is_prime = True
    for m in range(2, candidate):
        if (candidate % m) == 0:
            is_prime = False
            break
    if is_prime:
        print("%d is prime!" % candidate)


2 is prime!
3 is prime!
5 is prime!
7 is prime!

Issues with making a function:

  1. What does it do?
    • Finds all primes less than or equal to N
  2. What are the inputs?
    • Input 1: start range (int)
    • Input 2: end range (int)
  3. What are the outputs?
    • Output: list-int

In [9]:
# Our prime number example from week 1
N = 10
result = []
for candidate in range(2, N):
    # n is candidate prime. Check if n is prime
    is_prime = True
    for m in range(2, candidate):
        if (candidate % m) == 0:
            is_prime = False
            break
    if is_prime:
        result.append(candidate)
        
print(result)


[2, 3, 5, 7]

Some questions

  1. How can we recast this script as a component?
    • Inputs
    • Outputs
  2. Should the component itself be recast as having another reusable component?

Python Function Syntax

Transform the above script into a python function.

  1. Function definition
  2. Formal arguments
  3. Calling a function

In [11]:
def identify_primes(start_range, end_range):
    result = []
    for candidate in range(start_range, end_range):
        # n is candidate prime. Check if n is prime
        is_prime = True
        for m in range(2, candidate):
            if (candidate % m) == 0:
                is_prime = False
                break
        if is_prime:
            result.append(candidate)
    return(result)

identify_primes(2, 10)


Out[11]:
[2, 3, 5, 7]

In [12]:
identify_primes(5, 10)


Out[12]:
[5, 7]

Name Scoping in Functions


In [15]:
# Example 1: function invocation vs. formal arguments
def add_one(a):
    b = 10
    return a + 1
#
a = 1
b = 2
print("add_one(a): %d" % add_one(a))
print("add_one(b): %d" % add_one(b))


add_one(a): 2
add_one(b): 3

In [16]:
b


Out[16]:
2

Why is func(b) equal to 3 when the function is defined in terms of a which equals 1?


In [19]:
# Example 2: formal argument vs. global variable
def func(a):
    y = a + 1
    return y
#
# The following causes an error when False is changed to True. Why?
y = 23
func(2)
print("After call value of y: %d" % y)


After call value of y: 23

Why didn't the value of y change? Shouldn't it be y = 3?


In [22]:
# Example 3: manipulation of global variables
x = 5
def func(a):
    global x
    x = 2*a
#
#print("Before call value of x = %d" % x)
func(2)
print(x)
#print("After call value of x = %d" % x)


4

Why didn't the value of x change?

Refactoring a Function


In [31]:
def identify_primes2(start_range, end_range):
    result = []
    for candidate in range(start_range, end_range):
        # n is candidate prime. Check if n is prime
        if is_prime(candidate):
            result.append(candidate)
    return(result)

identify_primes2(2, 10)


Out[31]:
[2, 3, 5, 7]

In [27]:
def identify_primes2(start_range, end_range):
    result = []
    for candidate in range(start_range, end_range):
        # n is candidate prime. Check if n is prime
        if is_prime(candidate):
            result.append(candidate)
    return(result)

identify_primes2(2, 10)
# Return True if number is prime
def is_prime(candidate):
    is_prime = True
    for m in range(2, candidate):
        if (candidate % m) == 0:
            is_prime = False
    return is_prime

# Test cases
print("Should be prime %d" %is_prime(53))  # should prime
print("Should not be prime %d" %is_prime(52))  # should prime
print("0 input %d" % is_prime(0))


Should be prime 1
Should not be prime 0
0 input 1

Keyword Arguments

Functions evolve over time, and so it is natural that you'll want to add agruments. But when you do, you "break" existing code that doesn't include those arguments.

Python has a great capability to deal with this -- keyword arguments.


In [39]:
# Optionally check for negative values
def identify_primes3(start_range, end_range, is_check=True):
    if is_check:
        if start_range < 0:
            print("Bad")
            return
    result = []
    for candidate in range(start_range, end_range):
        # n is candidate prime. Check if n is prime
        if is_prime(candidate):
            result.append(candidate)
    return(result)

identify_primes3(-2, 10, is_check=False)


Out[39]:
[-2, -1, 0, 1, 2, 3, 5, 7]

In [5]:
# Extend find_primes to return None if argument is negative.

Exercise

  1. Find which substrings are present in a string.

  2. For example, consider the string "The lazy brown fox jumped over the fence." Which of the following substrings is present: "ow", "low", "row" and how many occurrences are there?

  3. Write a function that produces the desired result for this example.


In [40]:
a_string = "The lazy brown fox jumped over the fence."
a_string.index("lazy")


Out[40]:
4

In [48]:
def find_substrings(base_string, substrings):
    result = []
    for stg in substrings:
        if base_string.find(stg) >= 0:
            result.append(stg)
    return result

find_substrings("The lazy brown fox jumped over the fence.", ["azy", "jumped", "hopped"])


Out[48]:
['azy', 'jumped']

Function Driven Workflow

  • Script in a notebook
    • Create functions from scripts
  • Copy functions in a python module
  • Replace functions in notebook with use of functions in module
    • To use a function inside a notebook, you must import its containint module.

In [50]:
!ls


Functions.ipynb  identify_prime.py  Project-overview.pptx

In [51]:
import identify_prime
identify_prime.identify_primes(2, 20)


Out[51]:
[2, 3, 5, 7, 11, 13, 17, 19]