Functional Decomposition

I choose a lazy person to do a hard job. Because a lazy person will find an easy way to do it. – Bill Gates

Functions are certainly interesting. But why use them? Python doesn’t demand functions at all; you can certainly do the entire Pong lab, for example, (or anything else) without functions.

It turns out coding is quite repetitive. Often times, we want to do the same or very similar things over and over again; you could copy-paste the same code in every situation, or you could just write a function and call that instead.

This is why we have functional decomposition. The principle of laziness is actually highly valued in computer science. The tell-tale sign of an inadequate design is repetitive code. A good design minimizes code repetition.

Functions Type

You’ve seen functions in the notes. They look something like this.

def function_name():
    do stuff inside the function

Some simple examples.

def mileyCirus():
    print "Oh so cute! Such a talented little girl."
    print "Wow, she's really matured."
    print "She's gone crazy! I repeat, she's gone crazy!"

Last recitation, we discussed the four primitive types: int (Integer), float (Float), str (String), and bool (Boolean). I alluded to the fact that there were more than just these four primitive types. In fact, there are even types that are not primitive at all! Don’t concern yourself with the differences for now.

There is actually a separate type for functions!

>>> type(mileyCirus)
<type 'function'>

Wow! There’s actually a whole different type called function reserved just for functions. This is not a primitive type. Next time we’ll discuss the differences between primitive and non-primitive types, I promise. For now it’s enough to remember that the function type is not a primitive one, while the other four types are.

Functions can also return, or give back, certain values. These values, like all other values, also have types. Let’s examine the following function.

def beer():
    return "drunk"

What’s the return type of the function beer? A string. Not complicated. Pretty simple. How about a cooler example?

def howManyOscars(name):
    if name == "Gravity":
        return 7
    elif name == "Matthew McConaughey":
        return 1
    elif name == "Leonardo DiCaprio":
        return
    else:
        return "Invalid Name Yo!!"

So what is the return type of this function? Hmm, well the answer is complicated. Obviously, depending on the input, the return type will be completely different! In two cases, where name is “Gravity” or “Matthew McConaughey”, the return type is int. If name was “Ellen Degeneres”, on the other hand, the return type would be str.

But what if it was “Leonardo DiCaprio”? Hmm. I’ll give you a hint: it’s the same return type as the function mileyCirus.

Python as another type. This one is for the cases when there is no return value. It’s called NoneType, and it only has one value: None.

Really, this was just an excuse for me to point out that DiCaprio got None Oscars. Excuse me while I go cry for a bit.

Parameters and Scope

Functions aren’t just short-cuts for copy-paste. They are ways to dynamically interact with data, which is why we pass parameters. Parameters are variables given to a function that it can use to do more specialized tasks.

def mileyCirus(age):
    if age <= 13:
        print "Aww, she's such a talented little girl!"
    elif age <= 18:
        print "Wow, she's really matured."
    else:
        print "She's gone crazy! I repeat, she's gone crazy!"

In this function, the parameter age is used to determine just… erm, how crazy our favorite pop star has become.

A rather funny thing happens when you define a variable within a function, however. Let’s look at this code.

def miley():
    age = 15
    print "Phew! Not crazy yet..."

def jBeebs():
    print age

miley()
jBeebs()

When we run this code, we get the following error!

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in jBeebs
NameError: global name 'age' is not defined

This is a new kind of error. Last time, we saw the TypeError, which is what happens when the types of an expression don’t add up. This time we’re looking at a NameError, which is what happens when you try and access a variable but it’s not defined. In otherwords, Python has no idea what age is.

Why is this happening?

This brings up a new concept in programming called scope. The scope of a variable is the lines of code in the program where the variable can be accessed. There’s a simple rule that governs scope in Python: the LEGB Rule.

The LEGB rule stands for

Here, the L section of the LEGB rule is what’s causing our current problem. Because the parameter arg is defined within the function miley, which means that unfortunately, the function jBeebs does not know about it.

How can we fix this? By making age global. Here’s the fixed version.

age = 15

def miley():
    print "Phew! Not crazy yet..."

def jBeebs():
    print age

miley()
jBeebs()

Now that age is defined outside of any function, it’s defined within the global scope; in other words, it can be accessed within the entire file.

What do we mean by modules? For now, think of a module as a single file. If I created a completely different python file, then I wouldn’t be able to access the variable age. This is not quite accurate, but it’s close enough to what a module is for the time being.

Great! Now we can change our jBeebs function to do what we really wanted: to make Justin Bieber grow up (a seemingly impossible task, I know).

def jBeebs():
    print "before, I was only " + str(age) + " years old"
    age = 25
    print "now, I'm 25!"

So, that should work now, right?

References

The LEGB rule is a fantastic one that comes from the Mark Lutz and his rather fantastic book. I highly encourage you all to keep a copy for reference; it’s an excellent book. It goes over a great deal more of material that this class will skip over.