Functions

A function groups together instructions and gives them a name. They are the most important way to reuse code. Python functions are named after functions in math and, just like their mathematical counterparts, have inputs and outputs. Functions begin with the def keyword. Here’s an example of a function called triangle.

def triangle():
    """Draw a triangle"""
    from p4e.drawing import Turtle
    tu = Turtle()
    display(tu)
    tu.draw(70)
    tu.turn(120)
    tu.draw(70)
    tu.turn(120)
    tu.draw(70)
    tu.turn(120)

The triangle function is just a collection of statements that you’re already familiar with. The statements are indented inside the def statement to signify that they belong with the definition of triangle. There’s a string at the top called a docstring. The docstring describes what the function does.

Try typing the triangle function into the next cell:

[ ]:

Run the code in the cell. What happened?

The code above defines the function. Defining a function makes the name of the function available but does not execute the instructions inside the function. They are executed when the function is called or invoked. This code calls triangle:

triangle()

Call triangle in the next cell:

[ ]:

A fucntion definition is a compound statement.

Compound Statements and Whitespace

A compound statement is a is a statement that contains other code. The code inside of a compound statement begins after a colon and is indented to the right of the beginning of the statement.

Compound Statements

Here’s the pictured statement written into a cell:

[ ]:
def my_function():
    """Say Hello!"""
    a = 'Hello'
    b = 'World'
    print ('{}, {}!'.format(a,b))

print('This is my program.')
my_function()

Did the print statements come out in the oder you expected? Set a breakpoint on line 1 and step through the program. If you’re new to programming it might take you a while to wrap your mind around how execution jumps around now that you use functions. Practice using the debugger!

Python and Whitespace

The use of whitespace is controversial in Python. The statements inside of a function must be ligned up to the right of the function and even with each other. You can use either tabs or spaces but you can’t mix them. Use of whitespace in Python is designed to help you easily see the instructions that are inside and oustide of a function.

def line_up():
    print('This is inside')
    print('Also inside')
print('This is outside')
print('Still outside')

Most other programming languages like, for example C, C++, Java and JavaScript, use braces to signify the beginning and end of a function. The rule in those languages is that “whitespace doesn’t matter.” Here’s an example of “Hello World” in C++:

int main() {
    std::string my_name = "Boaty McBoatface";
    std::cout << "Hello " << my_name << std::endl;
}

That’s the way you’re supposed to write it. But, you could write it to look much worse:

int main(){std::string my_name="Boaty McBoatface";std::cout<<"Hello "<<my_name<<std::endl;}

Witespace in Python enforces a readable style. Not everyone likes that.

Function Arguments

Funtions are like machines that take inputs and produce outputs. The input to the function is called an argument and the output is called a return value. Function arguments are named in the function definition between the parentheses, they are separated by commas. Here’s an example of a function that takes one argument:

def say_hello(name):
    print(f"Hello, {name}")

Type the say_hello function into the next cell:

[ ]:

Now call your function in the next cell:

[ ]:

Can you see how the argument becomes the ``name`` variable? Now let’s see a function that takes two arguments. Here’s an example:

def name_tag(first_name, last_name):
    print(f"Hello, my name is {first_name} {last_name}")

Enter the name_tag function into the next cell:

[ ]:

Now call the name_tag function with two arguments:

[ ]:

Functions can have any number of arguments, zero or more. The names of arguments are used as variables inside of the function. Here’s an example of why arguments are so useful. This is the triangle function from before with an argument that controls the size of the triangle:

def triangle(size):
    """This triangle function takes an input: size"""
    tu = Turtle()
    display(tu)
    tu.draw(size)
    tu.turn(120)
    tu.draw(size)
    tu.turn(120)
    tu.draw(size)

Try updating the triangle function to take the size argument:

[ ]:

When you call a function that takes an argument you must specify a value for the argument. Call your function and change the size around:

[ ]:

It’s an error to call a function with the wrong number of arguments. Try it and see the error message that results. Functions can have any number of argumens. This function takes two arguments, name and color. The function uses HTML to print out the contents of name in the color of your coice:

from IPython.core.display import HTML

def html_nametag(name, color):
    """Print a nametag using HTML"""
    display(HTML(f'''<h1 style="color: {color}">Hello I'm {name}</h1>'''))

When you call the function you must supply a value for both name and color:

html_nametag("Mike", "red")

Try entering the html_nametag function and calling it.

[ ]:

Return Values

The return value is the output of a function. The return value is passed back to the place where the function is called. Inside of a function the return keyword causes a function to exit (or return) the flow of the program to where the function was called. Here’s an example of a function that returns a value:

def random_number():
    """I chose 4 completely at random."""
    return 4

When you call the function you use an equal sign to catch and store the returned value. In the code below the return value from random_number is stored in the variable num.

num = random_number()
print('num is:', num)

Enter the example code into the cell below and run it:

[ ]:

Can you see how the number 4 is returned to the place where the call is made?

The return statement stops the execution of a function, no code after the return statement is executed. Here’s an example of a function with a problem. The last print statement in the early_return won’t be executed:

def early_return():
    print("You see this.")
    return
    print("Not this.")

early_return()
[ ]:

Use the debugger to trace the function execution. A function can return any number of values. The values in the return are separated by commas. Here’s a function that returns three values:

def multiple_values():
    return "One", "Two", "Three"

When a function returns mutiple values all of the values must be received by variables on the left of the equal sign. Here’s how to catch all of the return vaules from the multiple_values function:

one, two, three = multiple_values()

Try entering and calling the multiple_values function:

[ ]:

Remember the syntax!

Mixing Arguments and Return Values

Most functions both take arguments and return values. Here’s an example of a function that adds two numbers and returns their sum.

def add_two_numbers(a, b) :
    c = a + b
    return c

The caller of the function supplies input and stores the returned output of the function:

s = add_two_numbers(10, 100)
print(s)

Here’s another example of a function that performs math. This does three operations and returns the results:

def do_math(a, b) :
  su = a + b
  pr = a * b
  ra = a / b
  return su, pr, ra

You can store multiple return values with multiple variables:

ret1, ret2, ret3 = do_math(12, 45)
print(f'The sum: {ret1}, product: {ret2} ratio: {ret3}')

Try entering the example functions.

Do you see that values are passed between different variables? Use the debugger to explore.

An Example Problem

I will ask you to write functions in assignments and on the midterm and the final. Writing functions to specification is a very common task in the life of a programmer. A function specification gives you the four important things you need to know when you write a function:

  1. The name of the function

  2. The function arguments

  3. The return value

  4. A description of what the function does.

Here’s an example speicification:

Example Problem

Write a function called power_of that takes two arguments, sig and power. The function should return the value: \begin{equation*} sig^\left( power \right) \end{equation*}

  • Name: power_of

  • Arguments:

    • sig (float) - The significand

    • power (float) - The exponent

  • Returns: The sig to the power power.

When you get a function description, start by setting up the problem. Create a function with a matching name and arguments. If there’s a return value add a return statement (even if it is not returning the right thing at first). Alwyas add a docstring. Here’s the start of the solution:

def power_of(sig, power):
    """The power_of function's docstring"""
    return 0 # Fix me later

Once setup, write code to call your function that you use to test. It’s essential to test your code as you go along. Write a line or two and then test again. Here’s some code to test the power_of function:

got = power_of(2, 5)
print(f"I got {got} and it should be 32")

Now that you have a start and a way to test your code you can begin to problem solve and finish the question. Don’t know how to do it. Try Google. Try searching for “python power operator”.

[ ]:

Designing with Functions

Functions give you a way to name snippets of code. The name of the function should succinctly describe what the function does. Here’s an example of a function that reads the first line of a file:

def read_first_line(filename):
    """Read the first line of a file."""
    file_handle = open(filename, 'r')
    line = file_handle.readline()
    file_handle.close()
    return line

Calling the function returns the first line of the file:

print(read_first_line('files/example.txt'))
[ ]:

The Importance of Docstrings

Functions have docstrings to help readers understand what they do. For complicated functions docstrings describe in detail the function arguments and return values. Here’s a docstring describing the read_first_line function.

def read_first_line(filename) :
    """
    Read the first line of a file.

    Arguments:
        filename - The name of the file to read.

    Returns: A str containing the first line of the file.
    """
    file = open(filename, 'r')
    line = file.readline()
    file.close()
    return line
[ ]:

The help function reads the docstring. Type in one version of the read_first_line function and use the help function to see the docstring.

help(read_first_line)
[ ]:

When it’s not obvious what the function arguments are you should be more descriptive in your docstring. It’s also important sometimes to describe what the return value is (or might be).

[ ]:
def read_a_line(filename, offset) :
    '''
    Reads a line in the file after seeking to a particular place.

    Arguments:
      filename - The name of the file to read.
      offset - The place in the file to start reading.

    Returns:
      A string containing the line that was read.
    '''
    file_handle = open(filename)
    file_handle.seek(offset)
    line = file_handle.readline()
    file_handle.close()
    return line

Now the help function shows a really useful message:

[ ]:
help(read_a_line)

There are official Python guidelines for how to use docstrings in functions:

It will be difficult at first to know what code you should put in a function. Don’t worry, it’s hard for everyone at first! Practice, practice, practice!

Function Variables and Scope

Variables created inside of a function, including the function arguments, are private to the function. That means they only exist while the funciton is executing and disappear after the function returns. For beginners this can be confusing, but it’s an essential feature of Python and other programming languages. The validity of a variable is called scope.

Global Variables

Variables that are defined all the way to the left (also called top-level or file scope) are global variables. Global variables are available everywhere in a program, including inside of functions. The code below demonstrates that the variable a can be used inside of the function:

[ ]:
# a is a global variable.
a = 10

def func(b):
    # b is private to the function and can
    # only be used inside of it.
    print(f'a is {a} and b is {b}')

# call func and pass in a+1
func(a+1)

Global variables sometimes lead to confusion. When a function uses a variable with the same name as a global variable the one inside of the function is used. Here’s an example where a name conflict has confusing results:

[ ]:
a = 10
print(a)

def func(a):
    a = a + 1
    print(a)

func(a)
print(a)

Can you explain what happens when you run this code? Remember there are two variables named a.

The Global Keyword

When you use a global variable inside of a function Python has to make a guess. Should I use the global variable or should I create a local variable? Python will always choose to make a local variable. The global keyword tells Python to use the global variable rather than create a new local one. Look at the difference between the two functions below:

[ ]:
# a is global
a = "global"

def local_a():
    a = "inside of local_a()"

def global_a():
    global a # Tell Python to use global "a"
    a = "inside of global_a()"


print(a)
local_a()
print(a)
global_a()
print(a)

Avoid Global Variables

Sometimes global variables are necessary but unless there’s no other way to do something it’s a good idea to avoid global variables. Global variables make programs harder to read and can lead to confusing problems. For example, this program has an error, but it almost looks right:

[ ]:
def add_two_numbers() :
    global a, b
    c = a + b

a = 10
b = 20
c = 0
add_two_numbers()
print(c)

Before you use a global ask yourself, “Can I do this another way?” If answer is yes then you probably should.

The main() Function

In the C programming language every program has a main function. The main function is called to start the program and when the main function exits the program ends. Python works differently, it just starts executing instructions in your file, starting at the top and ending at the bottom. This is great for small programs but as programs get more complex it’s a problem because all of the variables in your program are global variables.

From now on your Python programs should not use global variables and not have code outside of a function. Starting this week your program should use a template similar to the one below:

[ ]:
"""
CIS-15 Program with Functions
Mike Matera
"""

# It's always okay to have import lines at top-level
import sys


def main():
    """All code goes inside of a function."""
    print(f'Hello, my name is {sys.argv[0]}')


# These two lines call the main() function when your program
# is run from the command line. More on this later!
if __name__ == '__main__':
    main()

WARNING: Functions in Functions

In Python it’s posible to define a function inside of another function. DO NOT DEFINE A FUNCTION INSIDE OF ANOTHER FUNCTION unless you know what you’re doing. All of your functions should begin all the way to the left of the page.