# Functions in Python

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):
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
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
print(accum(1))
print(accum(2))``````

Use a nested function to make local variables persistent.

``````def accum_func():
total = 0          # initialize it
nonlocal total # do you remember nonlocal? total is the total above
total += x
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()``````

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

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
y1 = point1
x2 = point2
y2 = point2
dist = calc_distance(x1, y1, x2, y2)
print(dist)``````

## 16   Exercise: Yet another distance function

You didn’t like the `x1 = point1, y1 = point1, ...` 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
y1 = point1
x2 = point2
y2 = point2
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?``````

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
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

f = open(filename)
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

# 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

# now, release ds and band
del ds, band

return arr

# read elevation.tif into elev; of course, use your path to elevation.tif

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

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

## 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

ds = gdal.Open(filename)
band = ds.GetRasterBand(1)
del ds, band
return arr

def calc_average(arr):
# number of rows
num_rows = arr.shape
# number of columns
num_cols = arr.shape
# 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

# 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`.