Class 9: Functions

A painful analogy

What do you do when you wake up in the morning?

I don’t know about you, but I get ready.

“Obviously,” you say, a little too snidely for my liking. You’re particular, very detail-oriented, and need more information out of me.

Fine, then. Since you’re going to be nitpicky, I might be able to break it down a little bit more for you…

  1. I get out of bed
  2. I take a shower
  3. I get dressed
  4. I eat breakfast

Unfortunately that’s not good enough for you. “But how do you eat breakfast?” Well, maybe I…

  1. Get a bowl out of a cabinet
  2. Get some cereal out of the pantry
  3. Get some milk out of the fridge
  4. Pour some cereal into a bowl
  5. Pour some milk into the bowl
  6. Sit down at the table and start eating

“Are you eating with a spoon?” you interrupt. “When did you get the spoon out? Was that after the milk, or before the bowl?”

It’s annoying people like this that make us have functions.

FUN FACT: The joke’s on you, because I don’t even actually eat cereal. Maybe I don’t even get ready in the morning, either.

What is a function?

Functions are chunks of code that do something. They’re different than the code we’ve written so far because they have names.

Instead of detailing each and every step involved in eating breakfast, I just use “I eat breakfast” as a shorthand for many, many detailed steps. Functions are the same - they allow us to take complicated parts of code, give it a name, and type just_eat_breakfast() every morning instead of twenty-five lines of code.

What are some examples of functions?

We’ve used a lot of functions in our time with Python. You remember our good buddy len? It’s a function that gives back the length of whatever you send its way, e.g. len("ghost") is 5 and len("cartography") is 11.

len

Almost everything useful is a function. Python has a ton of other built-in functions!

Along with len, a couple you might have seen are:

  • abs(...) takes a number and returns the absolute value of the number
  • int(...) takes a string or float and returns it as an integer
  • round(...) takes a float and returns a rounded version of it
  • sum(...) takes a list and returns the sum of all of its elements
  • max(...) takes a list and returns the largest of all of its selements
  • print(...) takes whatever you want to give it and displays it on the screen

Functions can also come from packages and libraries. The .get part of requests.get is a function, too!

And here, to prove it to you?

max
print
import requests
requests.get
# And if we just wanted to use them, for some reason
n = -34
print(n, "in absolute value is", abs(n))

print("We can add after casting to int:", 55 + int("55"))

n = 4.4847
print(n, "can be rounded to", round(n))
print(n, "can also be rounded to 2 decimal points", round(n, 2))

numbers = [4, 22, 40, 54]
print("The total of the list is", sum(numbers))

See? Functions make the world run.

One useful role they play is functions hide code that you wouldn’t want to type a thousand times. For example, you might have used urlretrieve from urllib to download files from around the internet. If you didn’t use urlretrieve you’d have to type all of this:

def urlretrieve(url, filename=None, reporthook=None, data=None):
    url_type, path = splittype(url)

    with contextlib.closing(urlopen(url, data)) as fp:
        headers = fp.info()

        # Just return the local path and the "headers" for file://
        # URLs. No sense in performing a copy unless requested.
        if url_type == "file" and not filename:
            return os.path.normpath(path), headers

        # Handle temporary file setup.
        if filename:
            tfp = open(filename, 'wb')
        else:
            tfp = tempfile.NamedTemporaryFile(delete=False)
            filename = tfp.name
            _url_tempfiles.append(filename)

        with tfp:
            result = filename, headers
            bs = 1024*8
            size = -1
            read = 0
            blocknum = 0
            if "content-length" in headers:
                size = int(headers["Content-Length"])

            if reporthook:
                reporthook(blocknum, bs, size)

            while True:
                block = fp.read(bs)
                if not block:
                    break
                read += len(block)
                tfp.write(block)
                blocknum += 1
                if reporthook:
                    reporthook(blocknum, bs, size)

    if size >= 0 and read < size:
        raise ContentTooShortError(
            "retrieval incomplete: got only %i out of %i bytes"
            % (read, size), result)

    return result

Horrifying, right? Thank goodness for functions.

Writing your own functions

I’ve always been kind of jealous of len(...) and its crowd. It seemed unfair that Python made a list of cool, important functions, and neither me nor you had any say in the matter. What if I want a function that turns all of the periods in a sentence into exclamation points, or prints out a word a hundred million times?

Well, turns out that isn’t a problem. We can do that. Easily! And we will. If you can type def and use a colon, you can write a function.

A function that you write yourself looks like this:

# A function to multiply a number by two

It has a handful of parts:

  1. def - tells Python “hey buddy, we’re about to define a function! Get ready.” And Python appropriately prepares itself.
  2. double - is the name of the function, and it’s how you’ll refer to the function later on. For example, len’s function name is (obviously) len.
  3. (number) - defines the parameters that the function “takes.” You can see that this function is called double, and you send it one parameter that will be called number.
  4. return bigger - is called the return statement. If the function is a factory, this is the shipping department - return tells you what to send back to the main program.

You’ll see it doesn’t do anything, though. That’s because we haven’t called the function, which is a programmer’s way of saying use the function. Let’s use it!

print("2 times two is", double(2))
print("10 times two is", double(10))
print("56 times two is", double(56))
age = 76
print("Double your age is", double(age))

Function Naming

Your function name has to be unique, otherwise Python will get confused. No other functions or variabels can share its name!

For example, if you call it len it’ll forget about the built-in len function, and if you give one of your variables the name print suddenly Python won’t understand how print(...) works anymore.

If you end up doing this, you’ll get errors like the one below

def greet(name):
    return "Hello " + name

# This one works
print(greet("Soma"))

# Overwrite the function greet with a string
greet = "blah"

# Trying the function again breaks
print(greet("Soma"))

Parameters

In our function double, we have a parameter called number.

def double(number):
    bigger = number * 2
    return bigger

Notice in the last example up above, though, we called double(age). Those don’t match!!!

The thing is, your function doesn’t care what the variable you send it is called. Whatever you send it, it will rename. It’s like if someone adopted my cat Smushface, they might think calling her Petunia would be a little bit nicer (it wouldn’t be, but I wouldn’t do anything about it).

Here’s an example with my favorite variable name potato_soup

def exclaim(potato_soup):
    return potato_soup + "!!!!!!!!!!"

invitation = "I hope you can come to my wedding"
print(exclaim(invitation))

line = "I am sorry to hear you have the flu"
print(exclaim(line))

invitation and line both get renamed to potato_soup inside of the function, so you can reuse the function with any variable of any name.

Let’s say I have a function that does some intense calculations:

def sum_times_two(a, b):
    added = a + b
    return added * 2

To reiterate: a and b have nothing to do with the values outside of the function. You don’t have to make variables called a and b and then send them to the function, the function takes care of that by itself. For example, the below examples are perfectly fine.

sum_times_two(2, 3)
r = 4
y = 7
sum_times_two(r, y)

When you’re outside of the function, you almost never have to think about what’s inside the function. You don’t care about what variabels are called or anything. It’s a magic box. Think about how you don’t know what len looks like inside, or print, but you use them all of the time!

Why functions?

Two reasons to use functions, since maybe you’ll ask:

Don’t Repeat Yourself - If you find yourself writing the same code again and again, it’s a good time to put that code into a function. len(...) is a function because Python people decided that you shouldn’t have to write length- calculating code every time you wanted to see how many characters were in a string.

Code Modularity - sometimes it’s just nice to organize your code. All of your parts that deal with counting dog names can go over here, and all of the stuff that has to do with boroughs goes over there. In the end it can make for more readable and maintanable code. (Maintainable code = code you can edit in the future without thinking real hard)

Those reasons probably don’t mean much to you right now, and I sure don’t blame you. Abstract programming concepts are just dumb abstract things until you actually start using them.

Let’s say I wanted to greet someone and then tell them how long their name is, because I’m pedantic.

name = "Nancy"
name_length = len(name)
print("Hello", name, "your name is", name_length, "letters long")

name = "Brick"
name_length = len(name)
print("Hello", name, "your name is", name_length, "letters long")

name = "Saint Augustine"
name_length = len(name)
print("Hello", name, "your name is", name_length, "letters long")

Do you know how exhausted I got typing all of that out? And how it makes no sense at all? Luckily, functions save us: all of our code goes into one place so we don’t have to repeat ourselves, and we can give it a descriptive name.

def weird_greeting(name):
    name_length = len(name)
    print("Hello", name, "your name is", name_length, "letters long")

weird_greeting("Nancy")
weird_greeting("Brick")
weird_greeting("Saint Augustine")

return

The role of a function is generally to do something and then send the result back to us. len sends us back the length of the string, requests.get sends us back the web page we requested.

def double(a):
    return a * 2

This is called the return statement. You don’t have to send something back (print doesn’t) but you usually want to.

Writing a custom function

Let’s say we have some code that compares the number of boats you have to the number of cars you have.

if boat_count > car_count:
    print "Larger"
else:
    print "Smaller"

Simple, right? But unfortunately we’re at a rich people convention where they’re always comparing the number of boats to the number of cars to the number of planes etc etc etc. If we have to check again and again and again and again for all of those people and always print Larger or Smaller I’m sure we’d get bored of typing all that. So let’s convert it to a function!

Let’s give our function a name of size_comparison. Remember: We can name our functions whatever we want, as long as it’s unique.

Our function will take two parameters. they’re boat_coat and car_count above, but we want generic, re-usable names, so maybe like, uh, a and b?

For our function’s return value, let’s have it send back "Larger" or "Smaller".

# Our cool function
def size_comparison(a, b):
    if a > b:
        return "Larger"
    else:
        return "Smaller"
print(size_comparison(4, 5.5))
print(size_comparison(65, 2))
print(size_comparison(34.2, 33))

Your Turn

This is a do-now even though it’s not the beginning of class!

1a. Driving Speed

With the code below, it tells you how fast you’re driving. I figure that a lot of people are more familiar with kilometers an hour, though, so let’s write a function that does the conversion. I wrote a skeleton, now you can fill in the conversion.

Make it display a whole number.

def to_kmh(speeed):
    "YOUR CODE HERE"

mph = 40
print("You are driving", mph, "in mph")
print("You are driving", to_kmh(mph), "in kmh")

1b. Driving Speed Part II

Now write a function called to_mpm that, when given miles per hour, computes the meters per minute.

1c. Driving Speed Part III

Rewrite to_mpm to use the to_kmh function. D.R.Y.!

2. Broken Function

The code below won’t work. Why not?

# You have to wash ten cars on every street, along with the cars in your driveway.
# With the following list of streets, how many cars do we have?

def total(n):
    return n * 10

# Here are the streets
streets = ['10th Ave', '11th Street', '45th Ave']

# Let's count them up
total = len(streets)

# And add one
count = total + 1

# And see how many we have
print(total(count))

3. Data converter

We have a bunch of data in different formats, and we need to normalize it! The data looks like this:

var first = { 'measurement': 3.4, 'scale': 'kilometer' }
var second = { 'measurement': 9.1, 'scale': 'mile' }
var third = { 'measurement': 2.0, 'scale': 'meter' }
var fourth = { 'measurement': 9.0, 'scale': 'inches' }

Write a function called to_meters(...). When you send it a dictionary, have it examine the measurement and scale and return the adjusted value. For the values above, 3.4 kilometers should be 3400.0 meters, 9.1 miles should be around 14600, and 9 inches should be apprxoimately 0.23.