Functions

You've seen functions in math. For example:

$f(x) = 2x + 7$

This is the definition of $f(x)$. By itself it doesn't have a value. It just defines what it would mean to call the function $f$ on some value.

Given a value for $x$, we can calculate the resulting value of $f(x)$. $f(2)$ evaluates to 11, $f(-1$) gives 5.

In programming we also have functions. Python has some built-in functions.

Last week we saw the following code:

In [4]:
L = [1, 3, 5]
len(L)
Out[4]:
3

len is a function. It takes a list as its parameter and it calculates the length of the list. We say that it returns the number of elements in the list.

We've also seen that we can use a function in an expression. The function is evaluated and the resulting value is used in the expression.

In [5]:
x = len(L) + 5
print(x)
8

That means that we can nest functions (i.e. use one function as the parameter to another.) In the example below the function len is called on L and the returned value is used as the parameter to range.

In [6]:
for i in range(len(L)):
    print(L[i])
1
3
5

We can also write our own functions. To do this we will follow a bit of a recipe.

  1. Write an example call (or two) of how we will use the function we plan to write.
  2. Write the header for the function. That's the def line.
  3. Write the type contract -- the type for each parameter and the type of value that will be returned.
  4. Write a description of what the function does.
  5. Finally, write the code for the body of the function.
  6. Then test our function.

Let's write a function together that does our own version of calculating the absolute value of a number.

First we will show how we would call it.

In [7]:
my_abs(5)
my_abs(-6)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-7-583b20f0ad17> in <module>()
----> 1 my_abs(5)
      2 my_abs(-6)

NameError: name 'my_abs' is not defined

Of course my_abs isn't defined yet, so we can't actually call it. Instead just work out, what we would get. We sometimes preceed this example call with >>> to show it is the call from the prompt, and I showed this in class, but you don't really need to do it that way. Just make sure you actually write an example.

For each example, work out the return value that you are expecting.

my_abs(5) -> 5 my_abs(-6) -> 6 my_abs(-4.2) -> 4.2

Step 2: Write the header. And while you are at it, make the examples that you wrote be part of a triple-quoted string inside the function. Indent them below the header.

In [8]:
def my_abs(x):
    '''
    my_abs(5) -> 5
    my_abs(-6) -> 6
    my_abs(-4.2) -> 4.2
    '''

Step 3: Add a type contract. Our function takes a single parameter which is a number. It returns a number. So the type contract is (number) -> number. Let's add that into our function.

In [9]:
def my_abs(x):
    '''
    (number) -> number
    
    my_abs(5) -> 5
    my_abs(-6) -> 6
    my_abs(-4.2) -> 4.2
    '''

Step 4: Write the description. Put it inside the string. That string, by the way, is called the docstring.

In [10]:
def my_abs(x):
    '''
    (number) -> number
    
    Return the absolute value of the parameter x.
    
    my_abs(5) -> 5
    my_abs(-6) -> 6
    my_abs(-4.2) -> 4.2
    '''

Step 5: Code the body. This is where we write the python statements that will be executed when the function is called. The new statement for this lesson is return. When this statement is executed, the function immediately stops running and returns (or gives back) a value to the point in the code where the function was called.

Suppose we put the line return 5 as our function body. Notice that this line is indented to match the same indentation as the docstring.

Let's add the line and execute the code.

In [11]:
def my_abs(x):
    '''
    (number) -> number
    
    Return the absolute value of the parameter x.
    
    my_abs(5) -> 5
    my_abs(-6) -> 6
    my_abs(-4.2) -> 4.2
    '''
    return 5

When I run the code above, nothing prints. That's because the function is defined, but it isn't called. Let's call it now.

In [12]:
print(my_abs(5))
print(my_abs(-6))
print(my_abs(-4.2))
5
5
5
In [13]:
def my_abs(x):
    '''
    (number) -> number
    
    Return the absolute value of the parameter x.
    
    my_abs(5) -> 5
    my_abs(-6) -> 6
    my_abs(-4.2) -> 4.2
    '''
    return 5

Each time we call the function, we give a different value for the parameter. But each time it returns the value 5. That doesn't match the test cases from our examples. It's because we didn't do a very good job of writing our body.

Let's fix the body to use an if statement to return the correct value.

In [14]:
def my_abs(x):
    '''
    (number) -> number
    
    Return the absolute value of the parameter x.
    
    my_abs(5) -> 5
    my_abs(-6) -> 6
    my_abs(-4.2) -> 4.2
    '''
    if x > 0:
        return x
    else:
        return x * -1

And we will run it again on the example test cases.

In [15]:
print(my_abs(5))
print(my_abs(-6))
print(my_abs(-4.2))
5
6
4.2

Another Example: Converting Pounds to Kilograms

Generally in Canada, people know their weight in pounds. Most of the calculations for determining dosages and other medical information, ask for a patient's weight in kilograms. Write a function that will convert from pounds to kilograms.

First we write the example calls:

pounds_to_kg(2.2) -> 1 pounds_to_kg(1) -> 0.454

Then we add the header and indent these example so they are part of the docstring.

As part of writing the header, we have to give each parameter a name. We only have one parameter. Let's call it pounds.

In [16]:
def pounds_to_kg(pounds):
   ''' 
   pounds_to_kg(2.2) -> 1
   pounds_to_kg(1) -> 0.454 
   '''

Next add the type contract. Not a great example, since it is the same as for my__abs. But it won't always be this way.

In [17]:
def pounds_to_kg(pounds):
   ''' 
   (number) -> number
   
   pounds_to_kg(2.2) -> 1
   pounds_to_kg(1) -> 0.454 
   '''

Next add a description.

In [18]:
def pounds_to_kg(pounds):
   ''' 
   (number) -> number
   
   Return the equivalent number of kilograms for this many pounds.
   pounds_to_kg(2.2) -> 1
   pounds_to_kg(1) -> 0.454 
   '''

And finally write the body. To figure out how to do this. Think about what it was you did to work out the correct answer for the examples.

In [19]:
def pounds_to_kg(pounds):
    ''' 
    (number) -> number
   
    Return the equivalent number of kilograms for this many pounds.
    pounds_to_kg(2.2) -> 1
    pounds_to_kg(1) -> 0.454 
    '''
    return pounds / 2.2

Now test the function, by running it on the examples that you wrote earlier.

In [20]:
def pounds_to_kg(pounds):
    ''' 
    (number) -> number
   
    Return the equivalent number of kilograms for this many pounds.
    pounds_to_kg(2.2) -> 1
    pounds_to_kg(1) -> 0.454 
    '''
    return pounds / 2.2

print(pounds_to_kg(2.2))
print(pounds_to_kg(1))
1.0
0.45454545454545453

P2 Part 1: ideal_weight

Convert your program ideal_weight.py from the Week 1 homework to include a function to do the calculation of the ideal weight according to the Miller formula. Then use your function before deciding to print a warning message for the tall men.

Solution

In [21]:
def ideal_weight(height, sex):
    '''
    (number, string) -> number
    
    Return the ideal weight (in kg) for a person with this sex and this height (in inches), 
    according to the Miller formula
    
    ideal_weight(61, "male")-> 57.6
    ideal_weight(63, "female")-> 57.18
    '''
    
    if sex == "male":
        if height <= 60:
            return 56.2
        else:
            return 56.2 + (height - 60) * 1.4
    else:
        if height <= 60:
            return 53.1
        else:
            return 53.1 + (height - 60) * 1.36
        
# Let's test our function first on our test cases
print(ideal_weight(61, "male"))
print(ideal_weight(63, "female"))

# Then let's write a program using input to ask for the user's height and sex
user_height = float(input("please enter your height in inches "))
user_sex = input("please enter your sex (either male or female) ")

ideal = ideal_weight(user_height, user_sex)
if user_sex == "male" and user_height > 72:
    print("The Miller formula is not a good estimate for tall men.")
print("Your ideal weight according to the miller formula is", ideal, "kg.")
57.6
57.18
please enter your height in inches 63
please enter your sex (either male or female) female
Your ideal weight according to the miller formula is 57.18 kg.

P2 Part 2: Taking this medication?

Nursing home residents are often each taking numerous medications. Write a function that takes a list of medications (presumably taken by one person) and the name of a single medication and returns True if this medication is included in the list. For example if the list is ["Lasix", "Ativan", "Zoloft", "Synthroid", "Remeron"] and the single name provided is "Ativan", then your function should return True. But if your funtion is called with "Aricept" and the same list, it will return False.

Our First Incorrect Solution

In [22]:
def taking_med(medication_list, one_to_check):
    '''
    (list of string, string) -> bool
    
    Return True if one_to_check is found in medication_list, False otherwise.
    
    L =  ["Lasix", "Ativan", "Zoloft", "Synthroid", "Remeron"]
    taking_med(L, "Ativan") -> True
    taking_med(L, "Aricept") -> False
    '''
    
    for i in range(len(medication_list)):
        if medication_list[i] == one_to_check:
            return True
        else:
            return False

When we tried the solution above on our test cases. It failed the first one. We revised it as below.

In [23]:
def taking_med(medication_list, one_to_check):
    '''
    (list of string, string) -> bool
    
    Return True if one_to_check is found in medication_list, False otherwise.
    
    L =  ["Lasix", "Ativan", "Zoloft", "Synthroid", "Remeron"]
    taking_med(L, "Ativan") -> True
    taking_med(L, "Aricept") -> False
    '''
    
    for i in range(len(medication_list)):
        if medication_list[i] == one_to_check:
            return True
        
    # now we are done iterating over the list. So if we haven't returned True by this point,
    # one_to_check wasn't in the list and we should return False
    return False

This one passes our test cases. Notice how it can bail-out early if we find a match. But we can't know that the medicine we are checking isn't in the list, until we have examined every item. That means we can't return False until we have completed the for loop.