More on Functions¶
See also
Text: Chapter 3 (section 3.5-3.6)
Read time: 44 minutes (11179 words)
We introduced the basic concept of a function earlier. Now we want to look a bit deeper at what goes on inside a function and talk about another important topic: Scope (no, not the mouthwash!)
As we talked about before, a function is just a container holding a bit of code
and local variables it can use to do its work. Most functions have
parameters
which are just more variables, but these variable are special,
they are give values by the caller
of the function. The idea is simple. The
function is designed to work on whatever parameter
values the caller wants
to hand to it at any point in their processing. Think about that square root
function we used earlier. That function would not be very useful if all it
could do is figure out the square root of five. If we generalize
it so it
can work on any number, we can make that number a parameter
and decide what
number we want the square root of whenever we like.
Callers and Callees¶
Notice that I used the term caller
in the discussion above. Formally, we
say that our program calls
a function to make it do work. The callee
is
the function we call. (Remember our idea about kicking the box to wake up the
troll). The call just refers to the name of the function. The use of the
function name causes Python to stop what it is doing at the moment, and
activate the function. It waits until the function is done, then it presses on
with what it was doing previously. For example, suppose we had a magic function
we set up in our program that looks like this:
def magic(number):
...
return magic_number
Somewhere inside this magic
function, we expect the function to take the
parameter
known as number
and produce a new variable (known only inside
the function) called magic_number
. That final number is returned to the
caller.
The caller
of this function can use this function wherever they need to.
Since it returns a value, the actual call
to the function can happen in the
middle of an expression, in an assignment statement, or even in a call to
another function.
x = magic(5)
x = magic(5) + magic(7)
x = sqrt(magic(5))
sqrt(6.0)
The first two examples should be easy to figure out. But the last two are a bit odd. In the third one, the caller wants the square root of a magic number. We could have done this in two steps:
x1 = magic(5)
x = sqrt(x1)
That is correct, but it makes us invent a temporary variable
(one we really
only need for a short time) to hold the intermediate value. By placing the call
to magic
inside the call to sqrt
, we avoid needing another variable.
The code works like you would expect.
We process the assignment statement, but notice that there is a call to
sqrt
on the right side. The parameter
must be available to activate
this function. Unfortunately, the parameter
is another call, this one to
magic
. The call to magic also needs a parameter, and that parameter is in
the code as a literal
number. So, we call magic
with its parameter
and wait for it to return a magic number
. Once we have that, we can hand it
to the sqrt
function and wait for it to give us back an answer. That final
answer is placed in the container named x
. Phew!
Back to the final example above. What if we set up the function to return a value, but we used it in a way that does not seem to want the value. Python actvates the function, fetches the result, and throws it away. In this case, nothing really happens, except a bit of time was used up while the troll did his/her/it’s thing!
How parameters work¶
When the caller of a function hands the function a value in the parameter
list
, that value is passed into the function. The function sees it in a
container named whatever the def
line had on it. The names on the def
line are only really known inside the function. That is fine, since the caller
has no real need to know what is going on inside the box! This is a key idea
in programming. We separate the two worlds: caller’s world, and callee’s world.
Neither needs to know what is going on in the other world to do their jobs.
How about multiple parameters¶
Many functions need more than one parameter
to do their job. We simple list
the individual parameter names inside the parentheses, separating each with a
comma. When we call this kind of function, we list the parameter values, again
separating them with commas:
def silly_multiply(a,b):
return a * b
...
x = silly_multiply(4,5)
Keyword parameters¶
Python allows calling a function in a kind of odd way:
def loan_rate(principle, interest, term):
...
Here we defined names for the parameter in a normal way. We can call this function in different ways:
payment = loan_calcualtor(16000.00, 2.5, 60)
payment = load_calculator(term=60, interest = 2.5, principle=16000.00)
In that last example, Python figures out what is what by the names you provide on the call. Notice that the order of the parameters is not important when we do this.
Scope¶
In programming, scope
refers to where physically within the code of your
program certain names can be used. Each language has its own rules for setting
this up, but most are similar enough that we can explain how it works in a way
that will be useful in several languages (including C++, which you will take
later).
File scope¶
At the highest level of programming, you write code and save it in a file. The
tool that processes your code has no idea what other files might be part of a
larger program, so any names you use in the file must be completely defined
inside the file. This is called file scope
.
In languages like C++, which is
called a stongly typed
language, meaning the compiler must know exactly
what kind of thing the name refers to, using a name that has not been defined
in the file will generate an error. Python is not so picky. If you refer to a
name you have never mentioned before in your code, Python will try to create a
new container with that name. If the name is being used as though it was a
function, Python will complain, since it needs to know where the function can
be found before you can use it.
What this means is that Python may create brand new variable containers whenever you invent a new container name, but you cannot create a new function name unless that function is defined in the current file.
The import statement¶
In our early examples, we used an import
statement to introduce a function
name to Python, so we could call that function when we wanted:
from math import sqrt
This line tells Python that it should look in another file (named math
) and
find a name called sqrt
inside of that file. If that lookup succeeds, we can use that function
later in our code:
x = sqrt(5.0)
Let’s see Python getting annoyed. Without including the import
line, try
using the sqrt
function:
>>> x = sqrt(5.0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'sqrt' is not defined
Python had no idea what that name was all about. You have something to fix!
Finding files to import¶
By default, Python knows to look for files in a set of directories on your system. Most of these directories are set up when you install Python. To see the full list of places where it will look, do this:
>>> import sys
>>> print(sys.path)
['', 'C:\\Windows\\system32\\python32.zip', 'C:\\Python32\\DLLs', 'C:\\Python32\
\lib', 'C:\\Python32', 'C:\\Python32\\lib\\site-packages']
Note
The double back-slash stuff is Python dealing with Windows! Windows decided to use the back-slash to separate folder names in a path to a location on the drive. Everyone else uses forward slashes. Ask Bill why!
All of the directories in this list
are on the drive where Python was
installed. You can add to this list if you create your own useful files,
something we will look at later.
Finding out what names are available when importing¶
Once you import something, you can list the names available using this:
>>> import math
>>> dir(math)
['__doc__', '__name__', '__package__', 'acos', 'acosh', 'asin', 'asinh', 'atan',
'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', 'er
fc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gam
ma', 'hypot', 'isfinite', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log', 'log10', '
log1p', 'modf', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 't
runc']
It appears like we have a bunch of useful “math” finctions available in that file.
Note
We shall see later that we can import
either a file, or a specially
prepared folder contining a number of files. This later setup Python calls
a package
.
We can ask what kind of things these available names are by doing this:
>>> type(math.sin)
<class 'builtin_function_or_method'>
>>>
There is that funky notation again. The name math
refers to a file (or
directory) containing a bunch of names we can use. The math.sin
notation
tells Python which of those names we are interested in. If we use another form
of import, we get simpler notation:
>>> from math import sin
>>> type(sin)
<class 'builtin_function_or_method'>
>>>
Global variables¶
If you define variables outside of any function, those variables are called
global
. We can use them whenever we like in our code. These variables are
not available to code in other files unless we tell Python we want to do this
explicitly.
Global constants¶
What is a constant
anyway? Well variables can vary and constants cannot.
Many programming languages do not really let you create containers with values
that do not vary while a program runs. Python is one of those. (C++ does let
you set up such beasts). A common tactic in Python to set up something you
should not modify is to yell at yourself. Name the container in all capital
letters. (In email, all caps is how you yell!) The capital letters are to
remind you this is a special container you should not mess with!
Here is a common example:
PI = 3.1415926
The capital letters warn you that you should not modify this.
Note
Did you know that a king once got mad at PI, declaring it as an obnoxious number. So he ordered it to be given a value of 3! It really messed up the world of math!
Local variables¶
If you are writing the code inside of a function, any variables you create are
called local variables
. The parameters
are also local variables, except
they are given initial values by the caller. Those local variables are only
visible to the code inside the function. The outside world cannot use those
local names for their own purposes (except in that one special way where we can
refer to the names as keywords).
This is as it should be. As you write a good function, one you hope will be used in many programs, you do not want to worry that the names you are using might be in use in another part of the program.
Testing scope¶
Here is an example demonstrating how this works:
>>> global_variable = 'test'
>>> def func1():
... local_variable = 'batfeathers'
... print(global_variable)
... print(local_variable)
...
>>> func1()
test
batfeathers
>>> print(global_variable)
test
>>> print(local_variable)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'local_variable' is not defined
The local_variable is not known at this point since it is not visible to the outer code.
Here is an example of a setup that hides a global variable:
>>> global_variable = 'test'
>>> def func2():
... global_variable = 'junk'
... print(global_variable)
...
func2()
junk
The function used the local variable (wrongly named global_variable
. That
effectively hides the real global variable!
Suppose we really wanted to access the global variable and give it a new value:
>>> global_variable = 'test'
>>> def func2():
... global global_variable
... global_variable = 'junk'
... print(global_variable)
...
func2()
junk
print(global_variable)
junk
This time, we told Python that when we use the name global_variable
inside
the function, we really mean for it to use the original, global, name.
Explaining scope another way¶
Long ago and far away, I used a simple analogy to explain how all this scope stuff works. For Python, let’s lay it out this way:
Pretend that your file is laid out top to bottom and surrounded by a one way glass mirrored wall. The mirror is set up so that no one outside the file can see in. Now, surround each function inside your file by other one-way mirrored walls, set up so you can see out from inside the function, but not in from outside the function. Paint the name of the function on the outside of the wall.
With this setup, scope rules are simple. From any point in your code, inside or outside of a function, you can use any name you see by looking toward the top of your code. (You cannot look below where you are at the moment.) By the way, you can see around all the functions to code above them. You can see the names of the functions above you, but cannot see inside those functions.
From inside a function, you can see all names defined inside that function (local variables) and can see names defined outside the function above the function definition. If a name is defined in two places, you use the first one you find as you scan your code upward. That means if you use a name that was created inside the function, and that same name exists outside the function, you will use the local version, unless you force things as shown earlier.
Phew.
After a bit of writing code, you get into the pattern, and do not think about this much. Python can byte you, though, since it creates new variables whenever you assign a value to a new name.