Python Functions

A python program is made of many lines of code, stored in a file with a name like "example.py". It's natural to have a way to divide the lines code up into sensible sub-parts, and these are called functions. Almost all the code you work with in Python is inside a function.

Python Function Syntax - "def"

Suppose we have a Python program in the file example.py. The program text will be divided into many functions, each marked in the program text by the word def:

alt: program is made of functions, each with def

Here is an example Python function named "foo". This function doesn't do anything sensible, just shows the syntax of a function.

def foo():
    x = 1
    y = 2
    z = 3
    i = 0

Parts of a function definition:

Key points: A function starts with the word "def", has a name, and some lines of code

Function Call Sequence

Say we have a function named "caller", and its body lines are run from top to bottom in the usual way:

def caller():
    x = 6
    y = x + 1
    foo()   # call the "foo" function
    x = 7
...

To "call" a function is to invoke and run its lines of code. The list below traces the sequence of the caller() function running, calling the foo() function to run its lines, and then returning to finish the caller() lines.

  1. Line 1: x = 6 runs in the caller
  2. Line 2: y = x + 1 runs in the caller
  3. Line 3: foo() runs, calling that function
    -The run goes over to foo(), running the body lines there
    -Note the required parenthesis () to call the function
    -The run of foo() goes through its lines
    -When foo() is done, the run resumes in the caller code...
  4. Line 4 x = 7 runs in the caller

The run of lines in the caller code is suspended while called function runs. Or equivalently, we could say that the computer runs one function at a time, so when it's running foo(), it's not running caller() lines and vice-versa.

Here's a diagram showing the function-call sequence. The run starts with the lines in the caller function and goes over to run the lines in the called function. When the called function is finished, the run resumes where it left off in the caller.

alt: function call run goes from caller, to called, back to caller

Bonus fact: the variables, such a "x" above, are separate and independent for each function, so x in caller is a completely separate variable from x in foo. See function variables section below for more detail about variables.

Key point Call a function by its name with parenthesis added, like foo()

Variant: Obect-Oriented Noun.Verb Function Call

Another way to call a function is the object-oriented aka noun.verb style, like this:

    bit.move()

Here bit is Python data of some sort, and .move() is a function to run on that data. At its heart, this is still just a function call, but the data to work on is listed first, then a dot, then then the function name. Python mixes regular function calls and OOP function calls. Many languages use this "OOP" style, as it has a nice quality of thinking about what data you want to work on first, and then what operation to perform on that data.

Parameter Passing

Suppose we have a paint_window() function that fills a computer window with a color. We want a way to tell the function what color to use when calling it — blue or red or whatever.

A parameter is extra information supplied by the caller that customizes the run of the called function. We will learn all the details of parameters in time. For today, it's sufficient that a function can take a parameter, and the parameter value is "passed in" as part of the call simply by including the desired value within the parenthesis.

So for example to call the paint_window() function to paint the window blue might look like the this.

paint_window('blue')

The syntax 'blue' is a Python string, which is the way to express a bit of text like this.

Calling the function to paint the window yellow would look like:

paint_window('yellow')

A programmer would say the code "calls the paint_window function" and "passes in 'yellow' as the parameter".

You could say that paint_window is the verb we want the computer to do, and 'blue' or 'yellow' are noun modifiers we provide to the action.

Parameters in Parenthesis

The existence of parameters perhaps explains why the parenthesis are part of the function-call syntax — the parenthesis are the place for the parameter values when the function is called.

print() Parameter Example

This is a "in the interpreter" demo/example. There is a function in Python called print(). It does many things, but one simple thing it does is take in a parameter value and print it out to the screen. Though unglamorous, this is a way to see function call and parameters in action. Here are some calls to print() in the interpreter with their output (see also: interpreter to try this yourself).

>>> print('hi')     # Call print() passing in parameter 'hi'
hi                  # Output produced by print()
>>> print(23)
23
>>> print(4 + 5)
9

What you see here is calling the print() by its name as usual, and in between the parenthesis passing in parameter value like 'hi' or 23. The print() function take in that parameter value and prints it to the screen.

A function can accept multiple parameter values, and in fact print() accepts any number of parameters. The multiple parameter values are listed within the parenthesis, separated by commas, like this:

>>> print('hi', 22)
hi 22
>>> print('behold', 123, 'donuts')
behold 123 donuts
>>> print(4, 8, 15, 16, 'woot')
4 8 15 16 woot

See also: print

1. Function Call - Variables and Parameters

How do function call parameters work? Most often, a function call works intuitively, so you don't have to think about it too much. BUT when pushed, you need to know how it works to avoid getting tripped up.

1. The variables and parameters in each function are independent, sealed off from those in other functions. By default, an "x" in one function is independent of an "x" in some other function.

If variables in each function were not independent, functions could interfere with each other. Suppose a programmer is working on a function and uses a "total" variable. Then if another function somewhere else in the program also happened to choose the name "total". So would a run of one of those functions change the variable in use by the other function? That would be very hard to keep straight. Therefore, the default behavior is that variables introduced in each function are independent and sealed off from the variables in other functions, even if they have the same name. This also fits with the "black box" design, trying to keep each function independent and sealed-off from its environment.

2. Function call: parameter values come in by position within the ( .. ) (not by name or anything else)

Function Call Example

Say we have a "foo" function with 2 parameters, and it is called by a "caller" function later. What does the run of caller() below print? This is very detail oriented, but mercifully there's just a few lines.

def foo(x, y):
    x = x - y
    return x + 1


def caller():
    x = 2
    y = 3
    z = foo(y, x)
    print(x, y, z)

Solution

printed line:
2 3 2

Values passed in to foo(x, y) are 3 and 2, value returned is 1
The "x" within foo() is independent of the "x" in caller()

foo() y = 13?

What if we said y = 13 up in foo()? Would that change the output? No. Each function has its own "y", that would just change the "y" variable that is inside foo().

Try In Interpreter

It is unusual to write a def in the >>> interpreter, but here it's a live way to see that vars are independent for each function, and parameters match up by position.

>>> # 1. define foo(), it has its own "x" and "y"
>>> def foo(x, y):
...   x = x - y
...   return x + 1
... 
>>> 
>>> # 2. set variable "x" here, separate from "x" above.
>>> # Then call foo(), what is printed?
>>> x = 6
>>> y = foo(10, x)
>>> print(x, y)
???

What is printed? The variable x is still 6, since it is different from the x in foo(), so "6 5" is printed.

Function Variables

By default, the variables and parameters in each function are called "local" variables, and are independent, sealed off from variables in other functions. An "x" in one function is independent of an "x" in some other function. Most of the variables you use in Python code are local variables. There is another "global" type of variable which has some buggy tendencies and which most programs don't need, so we are not using global variables in CS106A. See the Globals section for more information.

Giving each function its own variables is makes sense if you think about the whole program. If variables were not independent, it would be easy for functions to interfere with each other. Suppose a programmer is working on a function and uses a "total" variable. Then if another function somewhere else in the program also happened to choose the name "total" for a variable, now we have a problem where the two "total" variables interfere with each other. That would be very hard to keep straight. Therefore, the default behavior is that variables introduced in each function are independent and sealed off from the variables in other functions. This also fits with the "black box" design, trying to keep each function independent, only taking in data through its parameters and returning its output.

Function Variable Example

The following example shows that function variables are independent. It also shows that function parameters match up by position, not by name.

Say we have a "foo" function with 2 parameters, and it is called by a "caller" function later. What does the run of caller() below print? This is a very detail oriented bit of code to trace through, but there's just a few lines.

def foo(x, y):
    x = x - y
    return x + 1


def caller():
    x = 2
    y = 3
    z = foo(y, x)
    print(x, y, z)

What does caller() print?

Solution

printed line:
2 3 2

Values passed in to foo(x, y) are 3 and 2, value returned is 1
The "x" within foo() is independent of the "x" in caller()

foo() y = 13?

What if we said y = 13 up in foo()? Would that change the output? No. Each function has its own "y", that would just change the "y" variable that is inside foo().

Try In Interpreter

It is unusual to write a def in the >>> interpreter, but here it's an interactive way to see how variables and parameters work for each function. You can type this in to the interpreter yourself.

>>> # 1. define foo(), it has its own "x" and "y"
>>> def foo(x, y):
...   x = x - y
...   return x + 1
... 
>>> 
>>> # 2. set variable "x" here, separate from "x" above.
>>> # Then call foo(), what is printed?
>>> x = 6
>>> y = foo(10, x)
>>> print(x, y)
???

What is printed? The variable x is still 6, since it is different from the x in foo(), so "6 5" is printed.

Global Variables

Global variables are variables defined outside of a function. It's easy for them to introduce bugs, so we don't use them in CS106A. Here is a quick introduction to global variables in Python.

A variable is created by an assignment =. A global variable is created by an assignment outside of a function. We have used this to create constants, which were in fact a type of global variable.

Python Constant Example

This code creates a global constant STATES, and then a function in that file can read the global without any extra syntax. The order within the file of the constant definition and the function does not matter. This sort of usage of a constant is fine, not a problem creating bugs.

STATES = ['ca', 'tx', 'nj', 'me']

def foo():
    for state_code in STATES:
        ....

Global Variable Example

Suppose instead we want to have a global variable called count, and when the increment() function runs, it increases the count. In the function, the line global count specifies that in this function, uses of count should not be a local variable, but should use the global variable of that name.

count = 0            # create global variable "count"


def increment():
    global count     # declare that we want the global
    count += 1       # access the global variable


def print_count():
    print('current:', count)

If a function just wants to read the global but not change it, no "global" declaration is needed. Python first looks for a local variable of that name, and if none is found, it falls back to looking for a global variable of that name. This is how the print_count() function above works - just using the variable name count, it automatically gets the global. If a function needs to set a global variable, the "global" declaration is required.

In fact, this idea that reading from the global variable works without any extra syntax is how constants work. Look at the STATES example above - STATES is just a global variable, and then the function can read from it.

Try Globals In Interpreter

You can try a version of the above in the interpreter to see it in action.

>>> count = 6
>>> 
>>> def increment():
...   global count
...   count += 1
... 
>>> 
>>> count
6
>>> increment()
>>> count
7
>>>
>>> special = 17
>>> def foo():
...   print('the special:', special)
... 
>>> foo()
the special: 17
>>>

Style vs. Global Variables

The problem with global variables is that they do not fit the black box model - now the run of a function is determined both by its parameters and the current state of the all the global variables the function uses. This can make design and debugging much more complicated. The best strategy is narrowing functions to just use their parameters, and this also makes the functions relatively easy to test, as we have seen with Doctests.

Note that constants do not pose this design problem. Since the constant does not change, it does not interfere with the black box function model.

 

Copyright 2020 Nick Parlante