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.
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
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
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.
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
printed out the default value of
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 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:
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!
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
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.
simply calls it
func and then calls it like it would any function. The scope
func is only within the
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.