Functions in Python

Dr. Huidae Cho
Institute for Environmental and Spatial Analysis...University of North Georgia

1   What is a function?

A function is a set of statements.

It can take arguments and return a value.

We mainly use functions to reduce redundancy in code.

We also use functions for better code readability.

Sometimes, we need them for recursive programming.

2   Defining a function

Define a simple function.

def f(x):
    y = 2*x
    return y
print(f(3))

Define it again.

def f(x): y = 2*x; return y
print(f(3))

3   Null function

Can you define a function with no body? Let’s try!

def nop():
nop()

Not working! Use the pass statement as a placeholder.

def nop(): pass # just do nothing
nop()

4   Functions are objects

A function is an instance of a class (object) with its name.

def foo():
    """ function document
    called doc string """
    return 'foo';
print(dir())    # lists all names in the current scope
print(dir(foo)) # lists all valid attributed for the foo object

Can you see the __class__ attribute? Try this.

def foo():
    """ function document
    called doc string """
    return 'foo';
print(dir())
print(dir(foo))
print(foo.__name__)
print(foo.__doc__)
print(foo.__call__())

5   Functions can be nested inside a function

We can limit the scope of a function to its parent function.

def some_math(x, y):
    def add(x, y):
        z = x + y
        print('{x}+{y}={z}'.format(x=x, y=y, z=z))
    def mul(x, y):
        z = x * y
        print(f'{x}*{y}={z}') # shorter way of formatting strings in Python 3.6
    add(x, y)
    mul(x, y)
some_math(10, 2)

6   Closures

Normally, any local variables defined inside a function do not persist.

def accum(x):
    total += x # well, there is even no way to initialize total
    return total
print(accum(1))
print(accum(2))

Use a nested function to make local variables persistent.

def accum_func():
    total = 0          # initialize it
    def add(x):
        nonlocal total # do you remember nonlocal? total is the total above
        total += x
        return total
    return add         # return the add function itself
accum = accum_func()
print(accum(1))
print(accum(2))

The above programming pattern is called closure.

7   Attributes

We can define function attributes.

def accum(x):
    accum.total += x
    return accum.total
accum.total = 0 # create a new attribute
print(accum(1))
print(accum(2))
print(accum.total)

8   Decorators

Add pre-/post-statements that wrap a function.

def decorator(func):
    def wrapper():
        print("before func")
        func()
        print("after func")
    return wrapper
def f():
    print("f")
f = decorator(f)
f()

# the "pie" syntax
# equivalent to g = decorator(g)
@decorator
def g():
    print("g")
g()

9   Revisit the persistent variable

We can use the decorator pattern to declare function attributes.

def static_decorator(name, val):
    def wrapper(func):
        setattr(func, name, val)
        return func
    return wrapper

# equivalent to accum = static_decorator('total', 0)(accum)
@static_decorator('total', 0)
def accum(x):
    accum.total += x
    return accum.total
print(accum(1))
print(accum(2))

10   Have you noticed this?

Functions can be variables.

def f(g):       # f takes a function and just returns it
    return g
def h(x):       # h takes an argument and prints it
    print(x)
a = f(h)        # a == h
a(12)           # calls h(12)

In C, we call them function pointers.

11   Exercise: Define your first function

Functions are a group of statements and expressions. They may return a value optionally using a return statement. Function definitions start with def, their name, and optional argument names with parentheses, followed by a colon. They take a logical block, meaning you have to indent the entire function body so it can belong to its function definition. Guess what this function does:

# define your function
def f():
    return "Hello"

# call your function and assign its return value to x
x = f()

# print x
print(x)

12   Exercise: Non-returning function

How is this function different from your first function that returned "Hello"?

# define your function
def f():
    print("Hello")

# call your function; this time f() does not return anything,
# so we don't have anything to assign to another variable
f()

13   Exercise: Add function

Let’s define a more useful function. What about an addition function?

def add(x, y):
    return x + y

z = add(10, 5)
print(z)

14   Exercise: Why functions?

Well, just adding two numbers is very trivial and you wouldn’t need to define a function for that. Then, why do we need functions? First, you can separate meaningful logic as a separate body. Second, when you have to repeat the same logic. Let’s see. Calculate the factorial of 5 (5!) and 10!.

##### without a function
fact = 1
for i in range(1, 5+1): # meaning from 1 to 5, NOT including 0 because multiplying fact by 0
                        # would give you 0.
    fact = fact * i
print(fact)

fact = 1
for i in range(1, 10+1):
    fact = fact * i
print(fact)
# what do you think? Both for loops are almost the same except 5 and 10, very repetitive

##### with a function
def factorial(n):
    fact = 1
    for i in range(1, n+1): # from 1 to n
        fact = fact * i
    return fact

print(factorial(5))
print(factorial(10))
# would you use the non-function version or the function version for your repeating task?
# also, we made it very clear that the for loop is for calculating a factorial by separating out
# the loop in a function

15   Exercise: Distance function

Let’s try geospatial computation. Oh! Function names can only contain alphabets, numbers, and underscores (_), and they cannot start with a number.

def calc_distance(x1, y1, x2, y2): # take two point coordinates
    return ((x1 - x2)**2 + (y1 - y2)**2)**0.5 # power to 0.5 means square root

point1 = (10, 20) # use a tuple because we'll never change the coordinates of point1
point2 = (100, 200)
x1 = point1[0]
y1 = point1[1]
x2 = point2[0]
y2 = point2[1]
dist = calc_distance(x1, y1, x2, y2)
print(dist)

16   Exercise: Yet another distance function

You didn’t like the x1 = point1[0], y1 = point1[1], ... part of the first distance function because you’ll have to repeat it when you need to calculate multiple distances? You can move that block to the function. Let’s try this.

def calc_distance(point1, point2):
    x1 = point1[0]
    y1 = point1[1]
    x2 = point2[0]
    y2 = point2[1]
    return ((x1 - x2)**2 + (y1 - y2)**2)**0.5

p1 = (10, 20)
p2 = (100, 200)
dist = calc_distance(p1, p2) # pass two point tuples
print(dist)

print(calc_distance((10, 20), (100, 200))) # equivalent

17   Exercise: File-writing function

Files can be created using the open(), write(), and close() functions. The open() function creates a file object who has the write() and close() functions. Let’s try.

def write_file(filename, content):
    f = open(filename, "w") # "w" means that open this file for writing; it'll overwrite the file
                            # if any; just calling f = open(filename) would attempt to read
                            # an existing file
    f.write(content) # write out your content to the file; here we use f that is returned from open();
                     # that's the connection between your content and the physical file
    f.close() # close the file; this is important!

# remember backslashes have special meanings, so you need to either repeat them twice ("c:\\test.txt")
# or use an r-string (r"c:\test.txt")
write_file(r"c:\test.txt", "Testing write_file()")

# let's see if open(..., "w") really overwrite an existing file
write_file("c:\\test.txt", "Testing write_file() second time")

# you can use forward slashes
write_file("c:/test.txt", "Testing write_file() third time")

# check c:\test.txt and what's in there?

18   Exercise: File-reading function

Can we read files? Of course! Let’s try this.

def read_file(filename):
    f = open(filename) # do you remember this function without "w"? yes, that's opening a file for reading
    content = f.read() # read the entire file content into content
    f.close() # again, it's important to close the file
    return content

print(read_file("c:/test.txt")) # what does it print?

19   Exercise: Directory-listing function

Let’s try to list all subdirectories and files in a directory. We’ll use globbing for listing all items in a directory.

import glob # need to import the glob module for globbing

def list_dir(dir):
    return glob.glob(dir + "/*") # find all (*) items in dir and return them as a list

for i in list_dir("c:/")
    print(i)

20   Exercise: Create number files

Using the write_file() function, let’s create 10 files with numbers.

def write_file(filename, content):
    f = open(filename, "w")
    f.write(content)
    f.close()

for i in range(1, 11): # from 1 to 10
    filename = "c:/num" + str(i) + ".txt" # do you remember type casting?
    write_file(filename, str(i)) # yes, the write() function only accepts strings, so you need type casting

# check your C:\; can you see num1.txt, ..., num10.txt

21   Exercise: Add numbers from the number files

Now, let’s try to add numbers from the number files using the read_file() function.

import glob # we'll use globbing to grab all num*.txt files

def list_num_txt_files(dir):
    return glob.glob(dir + "/num*.txt") # return all filenames starting with num and ending with .txt

def read_file(filename):
    f = open(filename)
    content = f.read()
    f.close()
    return content

x = 0
for filename in list_num_txt_files("c:/"):
    content = read_file(filename) # content is a string
    num = int(content) # type-cast content to int
    x = x + num # accumulate num to x

print(x) # print x; what is your x?

22   Exercise: Generate random integers

You can generate random integers using the random module.

import random

def generate_random_int(start, end): # this function returns a random integer between start and end
    return random.randint(start, end) # unlike range(), it returns an integer including start and end

for i in range(100): # print a random integer between 1 and 10, 100 times
    print(generate_random_int(1, 10))

23   Exercise: User-input function

Let’s define a function that says “What did you eat yesterday? (p)izza, (b)urger, (s)teak, (o)ther”, reads, and returns a user response.

def what_did_you_eat(): # doesn't need any arguments
    while True: # we go into an infinite loop until the user picks one of the right choices (p, b, s, o)
        # do you remember the input() function?
        you_ate = input("What did you eat yesterday? (p)izza, (b)urger, (s)teak, (o)ther ")
        if you_ate == "p":
            you_ate = "pizza" # return a full name
            break # break out of the while loop because p is one of the choices
        elif you_ate == "b":
            you_ate = "burger"
            break # break out of the while loop because b is one of the choices
        elif you_ate == "s":
            you_ate = "steak"
            break # break out of the while loop because s is one of the choices
        elif you_ate == "o":
            you_ate = "other"
            break # break out of the while loop because o is one of the choices
        else:
            print("Please use one of p, b, s, and o only")
    return you_ate

i_ate = what_did_you_eat()
print(i_ate)

24   Exercise: Plot a GeoTIFF file

Download elevation.tif and try the following code. Make sure to change the path to elevation.tif to yours.

# we'll use the gdal module
from osgeo import gdal

# we'll use matplotlib.pyplot to plot raster arrays; here, "as plt" creates
# a shortcut plt for its full name matplotlib.pyplot
import matplotlib.pyplot as plt

def read_geotiff(filename):
    # read in filename as a GDAL dataset object
    ds = gdal.Open(filename)

    # get the first and only band; note here that band numbering is 1-based
    band = ds.GetRasterBand(1)

    # read the band as a matrix
    arr = band.ReadAsArray()

    # now, release ds and band
    del ds, band

    return arr

# read elevation.tif into elev; of course, use your path to elevation.tif
elev_arr = read_geotiff("elevation.tif")

# plot elev in memory using the "im"age "show" function
plt.imshow(elev_arr)

# show the in-memory plot
plt.show()

plot-geotiff.png

25   Exercise: Calculate the average cell value of a GeoTIFF file

Download elevation.tif and try the following code. Make sure to change the path to elevation.tif to yours. We use the numpy.ndarray.sum method to calculate the sum of raster values.

from osgeo import gdal

def read_geotiff(filename):
    ds = gdal.Open(filename)
    band = ds.GetRasterBand(1)
    arr = band.ReadAsArray()
    del ds, band
    return arr

def calc_average(arr):
    # number of rows
    num_rows = arr.shape[0]
    # number of columns
    num_cols = arr.shape[1]
    # number of cells
    num_cells = num_rows * num_cols
    # sum of array cell values; sum() is a method in a NumPy array
    sum_arr = arr.sum()
    # average array cell value
    avg_arr = sum_arr / num_cells
    return avg_arr

elev_arr = read_geotiff("elevation.tif")
# calculate the average elevation and type-cast it to int
avg_elev = int(calc_average(elev_arr))

print("The average elevation is", avg_elev)

26   Homework: Say rock paper scissors

Define a function called i_rock() that generates a random integer between 1 and 3, and returns rock if the generated number is 1, paper if 2, and scissors if 3. This function doesn’t need to take any arguments and it returns one of three strings rock, paper, and scissors based on a random integer it generates. Just submit your function definition only in FirstLastname_i_rock.py.

27   Homework: Read the user’s rock paper scissors

Define a function called you_rock() that prints “(r)ock, (p)aper, (s)cissors, (q)uit? ”, reads a string from the user using the input() function, and returns rock if the input is r, paper if p, scissors if s, and quit otherwise. Just submit your function definition only in FirstLastname_you_rock.py.

28   Homework: Rock paper scissors game

Keywords: functions

Use the random.randint(), print(), input(), and your own functions to implement the rock paper scissors game.

  1. Define a function called i_rock() that generates a random integer between 1 and 3 using random.randint() and returns rock if the random number is 1, paper if 2, and scissors if 3
  2. Define another function called you_rock() that prints “(r)ock, (p)aper, (s)cissors, (q)uit? ”, reads an input string using the input() function, and returns rock if the input is r, paper if p, scissors if s, and quit otherwise
  3. Write an infinite while loop that does the following:
    1. Call i_rock() and store its return value in my_throw
    2. Call you_rock() and store its return value in your_throw
    3. Break out of the while loop if your_throw is quit
    4. Now, your_throw is not quit because you didn’t break the while loop in the above step
    5. Compare my_throw and your_throw, and print “You won”, “You lost”, or “We tied” using if-elif-else branching

Submit your script in FirstLastname_rock_game.py.

29   Homework: Advanced rock paper scissors game

Keywords: functions

Use the random.random(), print(), input(), and your own functions to implement the rock paper scissors game. There is one little problem. Your code has a bad habit of throwing a rock twice more likely than the other two.

  1. Generate a random number between 0 and 1 using random.random()
  2. Determine the computer’s rock (coded as 1), paper (2), or scissors (3) based on the random number, but how can you code your code’s bad habit? Think about that
  3. Pass the computer’s throw from step 2 to the play_once() function that does the following:
    1. Print “(r)ock, (p)aper, (s)cissors, (q)uit? ” and read a string input
    2. If the input is q, return 0
    3. Return 1 if the user won
    4. Return 2 if the user lost
    5. Return 3 if both players tie
  4. Get the return value of play_once()
  5. If the return value is 0, print Wins: **%, Losses: **%, Ties: **% and stop
  6. Print You won, You lost, or You tied and count wins, losses, and ties
  7. Go to step 1

Submit your script in FirstLastname_advanced_rock_game.py.

30   Homework: Arithmetic calculator

Keywords: functions, keyboard input, looping, slicing, branching

Write a simple calculator that supports four basic arithmetic operations (+, -, *, and /) for two floating-point numbers.

  1. Print “? ” and read an expression
  2. Pass the expression to your function called evaluate()
    1. If the expression is quit, return None
    2. Split the expression into three parts: left operand, operator, and right operand
    3. Branch based on the operator
    4. Calculate the expression and return its result
  3. Take the return value of evaluate()
  4. If the return value is None, stop
  5. Print the result
  6. Go to step 1

Submit your script in FirstLastname_calc.py.