Saturday, February 22, 2014

Conjunction Junction, what's your function?

Functions.... Functions are kind of like little miniature programs, that you can run over and over from within a program. We have already experienced some functions in previous posts. Functions are always characterized by their parentheses ( ). So len( ) is a function, raw_input( ) is a function, sorted( ) is a function, dir( ) is a function. If you play video games, jumping, or attacking, or moving would all be different types of functions. Usually, functions have a definition, some arguments, and a return statement. The definition is the only item that is necessary for a function to work, though you should also always add a return statement even though it is not required. So, to start, we'll make a really simple function.
def sayHello( ):
    print "Hello, nice to meet you."
The first thing you should notice when you complete the code is that nothing happens. The reason is that we aren't writing the function to be run immediately, we are "defining" a function to be stored in memory that we can run whenever we need it. In order to run it simply type sayHello( ). The first line is where we tell the interpreter that we are defining the function, by saying
def  <function name>( ):
Def is short for define, function name can be whatever you want it to be, but it should describe what the function does. Everything that comes after the definition statement is the code that will be run when the function is called. Now we'll create a function that takes an argument.

Let's recreate the len( ) function. I'll demonstrate the code and then we'll go through it.
def length( item ):
    length = 0
    for x in item:
        length += 1
    return length
That should do it, remember to keep your indent levels consistent.  Arguments are the term for anything that you "give" to the function in order for it to run. In our case we are giving a sequence style object (string, list, or tuple) to the function and it is measuring the number of elements in the sequence. It is important to note that the object the function uses in its code block is not the same as the object that is given as an argument. That is a confusing statement so I'll explain a bit more using our length( ) function.

Let's say we have a string object, myString, with a value of "abc". Then we call our function with length( myString ). The interpreter returns a value of 3. 
What actually happens internally is the interpreter sees that we are calling a function called length with an argument called myString. The interpreter then finds the length function in memory and sees that it accepts an argument called item. So it takes the myString object and copies it to a new object called item. Then it runs the code on the item object leaving the myString object untouched. This isn't so important in this example, but for our next example we'll write a function where it does matter.

Let's try to recreate the reverse method that is built in to list objects.
def reverse( myList ):
    newList = [ ]
    total = len(myList) - 1
    while total >= 0:
        newList.append(myList[total])
        total -= 1
    myList = newList
    return myList
To test, let's create a list object called x and set it equal to [9,8,7,6,5,4,3,2,1]. Then run the reverse function on it. Now type print x.
So you see that even though the function returns the same argument that it receives it does not affect the original object. This is because as soon as the function begins running it creates a copy of the argument, rather than performing any modifications to the argument itself. However, you can set the argument object to the return value of the function. That's also confusing... Let me give an example
x = reverse(x)
Doing this will set the x object equal to the return value of the reverse( ) function.
You can also write functions that take more than one argument by simply separating the arguments in the parenthesis with a comma. You can add as many arguments as you need:
def add(a, b):
    return a + b
That brings us to return statements. Return statements do two very important things: they dictate what the function will return to the interpreter and they terminate the function. So when we run our reverse( ) function it returns myList, which is a list object, so we know that anytime we run the function, we will get a list object in return. However, you can have multiple return statements in a function, and each return statement can return a different type of object or even no object at all. To demonstrate I'll quickly rewrite our sayHello( ) function.
def sayHello( ):
    print "Hello, nice to meet you."
    return
Adding the blank return statement is not necessary but it is also not detrimental, and it let's anyone who is reading my code immediately see that the function only does one thing and then terminates. It is just good practice to include return statements, even if the function doesn't actually return anything.

A function that might have multiple return statements would be something like a function that tests whether a number is even or not.
def isEven(n):
    if n % 2 == 0:
        return True
    else:
        return False
Another good practice is to check your arguments in your function to avoid errors, return statements can be useful in this regard as well. For instance, our length( ) function is supposed to accept a sequence style object as an argument. If you call the function with an integer as the argument, the function will error out. But the error might not make it obvious what has gone wrong. Let's redefine the length( ) function to check the input before running any code. If the input is not correct, we will tell the user what the problem is and terminate the function to avoid errors.
def length( item ):
    if type(item) == str or type(item) == tuple or type(item) == list:
        length = 0
        for x in item:
            length += 1
        return length
    else:
        print "This function requires a string, tuple, or list, as an argument."
        return

Now the function will only run if it receives the input it is looking for. This is also good practice when writing code. The more you minimize errors, the better. If errors do occur, then it is nice to have an explicit explanation of what went wrong so you can easily go back and fix the problem.

Two mores thing I'd like to go over before wrapping this up: keyword arguments and splat arguments. Keyword arguments are "optional" arguments you can supply to a function. When I say optional, I mean the function user doesn't have to supply the argument, because it has a default value. However the argument is still necessary for the function to work properly. I'll give an example by writing a function that takes a sentence and returns a word from the sentence.
def getWord(sentence, index = 1):
    wordCount = 0
    letterCount = 0
    start = 0
    for letter in sentence:
        letterCount += 1
        if letter == " ":
            wordCount += 1
            if wordCount == index:
                return sentence[start:letterCount - 1]
            else:
                start = letterCount
    return sentence[start:]

Now, when we run the function with only a sentence argument, it returns the first word because the index keyword argument that we specified in the definition line has already been set to a value of 1. If we want to get a specific word, we can reset the index keyword argument to whatever word we want.

Finally, splat arguments. Splat arguments are what you use when writing a function that takes an indeterminate number of arguments. For instance, if you are writing a function that adds some numbers together, but you don't know how many numbers there will be use a splat argument in your definition like so:
def addNumbers(*args):
    sum = 0
    for n in args:
        sum += n
    return sum

Note that the splat argument has a '*' in the definition line, but is used like a standard sequence object in the code block. The interpreter takes the splat argument and creates a tuple of all of the arguments supplied to the function. So when dealing with the splat argument in the function's code block you can treat it just like any other tuple.

There is a lot more to say about functions, but this should be a pretty good introduction to writing your own functions. We've covered all the basics of function writing (and then some). Not so bad eh? Functions are one of the most important and often used concepts in programming and we will be dealing with them a LOT, so if you don't get it all now, don't worry, we'll cover functions again and again as we go forward.

No comments:

Post a Comment