Recall how in the first Python lesson we looked at the while
loop and saw how it allows us to repeat instructions to the computer as many times as you want.
The next step up from a loop is a function, which allows us to wrap up a series of commands into a single command on its own. Let's take a look at an example.
def plus_one(number):
return number + 1
That code gives us the basic form of a function. Let's break it down.
First is the
def
keyword, which just say that we're defining a function.following that is a space, and then the name of the function, in this case
plus_one
immediately following that (no space) are names for the parameters that the function accepts, separated by commas and enclosed in parentheses. This function takes just one parameters, named
number
.following that is a colon signifying the beginning of a new scope --- we'll talk about the concept of scope later, but just think of it as a new block like in a loop.
following that is the body of the function, indented as per usual. Each of the parameters to the function is available as a variable in the body. This function just has one line in the body, and it adds 1 to whatever is assigned to
number
.this function, and most functions, end with a
return
statement: a statement that starts with the keywordreturn
and then includes an expression that evaluates to some value. This is the value that, as the name suggests, the function returns. In this case, returns means that the function evaluates to it when it's executed.
Let's take a look at what happens when we execute it.
two = plus_one(1)
print(two)
We call a function by entering its name followed by parentheses containing values for the function's parameters---we call these values arguments. So here, we're calling the function plus_one
with the value 1
. That value gets passed into the body of the function attached to the name in the appropriate place in the list of parameters, so it gets assigned number
in the function.
The function then executed, and evaluates to its return statement, if any. Here, of course, that just means that it evaluates to the number 2, which is assigned to the variable and printed as normal.
Functions don't need to have any parameters:
def cat_noise():
return "MEOW"
print(cat_noise())
They also don't need to have a return statement. If they don't have a return statement, they return the special value None
.
Why would you want to have a function with no return statement? Well, some functions can do things that don't require returning. For example, they might modify other values in memory (though that gets a little complicated and we'll talk about it later) or they might print something, make an internet request, delete a file, etc.
def say_cat_noise():
print("MEOW")
my_variable = say_cat_noise()
print(my_variable)
Of course, functions can have multiple parameters.
def greet(name, punctuation):
greeting = "Hello " + name + punctuation
return greeting
print(greet("Leonidas, the best cat", "!!!!"))
print(greet("Rex", "."))
Normally you pass arguments by position, i.e., the first argument is the first parameter in the function, and so forth, and so forth. But you can also pass arguments by name, by putting the names in the call to the function and then an equals sign. If you do so, you don't have to do it in order.
print(greet(punctuation="?", name="students"))
Normally, you get an error if you pass fewer arguments to a function than its number of parameters.
greet("nobody")
However, you can define a function with default arguments for some parameters, which get applied if you don't give an argument for that spot.
def greet2(name, punctuation="!"):
return "Hello " + name + punctuation
print(greet2("Dean Washburn"))
print(greet2("President Harreld", "?!!?"))
Scope¶
Learning about functions also requires understanding the really important concept of scope. Every variable exists in a scope, and isn't defined, or has a different definition, outside of that scope.
Functions create their own scope. What that means is that a variable defined in a function, or passed into it as one of its arguments, doesn't exist outside that scope. Here's an example:
x = 5
y = 100
def plus_10(x):
y = 50
print("inside the function, y = " + str(y))
return x + 10
print(x)
print(plus_10(20))
print(x)
print(y)
What we can see here is that even though we assigned x
and y
to name in the scope of the plus_10
function, that didn't touch the values of those variables assigned outside the function. We got to use those values inside the function without clashing with the external names. This is how scope works.
If a function can't find a variable in its own scope, it looks outside in its enclosing scope for it. Here's another example:
def plus_100(x):
return x + y
print(plus_100(50))
y
wasn't defined in the body or the arguments of plus_100
, so it went out into the global scope to find a value for it. Since we'd assigned y
to 100
earlier, it was findable by the function.
Let's reassign it now.
y = 99
plus_100(50)
When we called plus_100
again, it had to go looking for y
in the global scope again, and it found a different value! So it used that.
This is also a good practical programming clue for you. It's a bad idea to rely too much on variables in global scope (called global variables), because if you accidentally change their names, it can lead to surprising bugs, like a function that you meant to add 100 to things actually adding 99 to them.
So what if you actually want to change a global variable inside a function? Well, you could use the global
declaration in the body of the function, before you refer to that variable, in order to do that. But that's a bad idea, don't do it.
# if you find outself tempted to try something like this,
# you usually should rethink your code.
def insist_on_plus_100(x):
global y
y = 100
return x + y
y = 99
print(y)
print(insist_on_plus_100(50))
print(y)
You can have nested scope---scope within scope within scope. The outermost scope is the global scope, and a reference to a variable will look within the its own scope, and then within the next outer scope, and the next, and so forth, until it gets to the global scope.
x = "global scope"
def inner():
x = "innermost scope"
print(x)
def middle():
x = "middle scope"
inner()
print(x)
def outer():
middle()
print(x)
Can you guess what calling outer()
will do? Take a minute to figure it out, and then run this code yourself and see if you're right.