More Functions

Functions

Return values and statements

I mentioned in the rules to make functions that you can return values. We have seen functions like len() that tell us how long a string is. len() is returning a value to us. We can store that value in a variable or pass that returned value immediately to a function. Any time a function needs to let its caller know the results of its computation, it should return those results: return first_number + second_number

None

Every function returns a value even if it does not explicitly have a return statement. Python will insert a return statement for any function that doesn't have one: return None. None is a special data type. Instead of having multiple values like other data types like integers or booleans, NoneType can only be one value: None. Any function that returns None is called a void function because it is void of any substantial return value. The value of None is like a black hole because it is simply the absence of a value.

Keyword arguments

When we create functions we give the parameters names. This is so that we can reference those parameters in our function. Well you can also use those names when calling the function. So far we have only given functions the parameters in the order that the function asks for them but if we know the name of the parameters we can use those when we call the function.

def greet(message, person='Stranger'):
    print("%s, %s" % (message, person))

greet('hello there')                          # hello there, Stranger
greet('hello there', 'Reed')                  # hello there, Reed
greet(person='Reed', message='hello there')   # hello there, Reed

In our function greet we have a message and a person. The first parameters is what is called a positional argument because it must be first if no keywords are used so position matters. The second parameter provides a default value if it is not supplied which is why the first time we called the function greet it printed out the default value of Stranger.

The last time we called the function greet though we switched things up. We put the second parameter first and the first parameter second. We are able to do this because we specified their names when calling the function. You might wonder why we would do that. Well, in this case it made the function invocation (the time we called it) easier to read. That last invocation reads something like this, "Greet the person 'Reed' with the message 'hello there'." We also use keyword arguments when there are multiple parameters and we only want to provide the function with a couple of values because we are okay with the defaults. If we don't specify keywords when we call the function, then everything must be positional which means we need to provide all of the parameters.

Scope

Scope is a word we use to indicate where a named object is defined. So far, all of our variables have been in what we call global scope. This means that every variable we defined was available everywhere in our program (after we declared it, of course). When we create functions we create a new scope. This is called a local scope. From within a local scope we can see all of the named objects from global scope but not the other way around. Global scope cannot see local scope objects. You can even create a local scope within a local scope. The child scope can see what is in the parent scope but the parent cannot see what is in the child. A great explanation of Python scope can be found here:

StackOverflow Answer

Overriding global scope

When we create a function we get a new local scope. This local scope may create names that already exist in a parent scope or on the global scope. This is okay because the program will simply find our local scope version of that variable instead of the global version. Think of this as a stack of papers. When we enter a function we will put a piece of paper on the stack for every variable in the function. When we want to use a variable we will look through the stack of papers from top to bottom and as soon as we find the name we are looking for we will stop. So the variables in the function will get used before the variables outside of the function (if they had the same name). This is called shadowing. Also, when we exit the function we will throw away all of the papers that we put on the stack when we entered the function. This means that our local scope is destroyed. All of the variables that existed in that local scope will be gone. This is a big reason why we should return variables from functions. They will be destroyed if we don't give them back to the callers of the function.

Try creating a global variable and then overriding it in a function you create!

Higher-Order Functions

So, we have given functions that take and return strings, ints, and bools. We can even give and return lists and dictionaries to and from functions. What if we were to give a function to a function? It turns out that you can pass functions to and return functions from other functions. Functions that take or return other functions are called higher-order functions. Lets take a look at a simple example of this.

def say(message):
    print('"%s", said, Reed'.format(message))

def lights_off(func):
    func("Who turned out the lights")

lights_off(say)

This example defines two functions and then gives one of them to the other. In this example lights_off is the higher-order function because it accepts and calls the function. You will notice that just like with variables, the function does not care what anyone else called the function it was given. lights_off simply calls it func and then calls it like it would any function. The scope of func is only within the lights_off function.

This is more of an advanced topic but the pattern is used all of the time so it would be good to become familiar with it. John DeNero's "Composing Programs" website has a great article on higher-order functions that would be great to read to learn more.

Last updated