Class 9C: Programming in Python II#
We will begin soon!
from IPython.display import IFrame
from IPython.display import Markdown
# Additional styling ; should be moved into helpers
from IPython.display import display, HTML
HTML("<style>{}</style>".format(open("rise.css").read()))
Programming in Python I#
In this class, we go through a notebook by a former colleague, Dr. Mike Gelbart, option co-director of the UBC-Vancouver MDS program.
If you prefer, you can also watch his recording of the same material.
Announcements#
Here is the plan for the next couple of weeks:
Week 10 (next week):
Monday March 13th 10:00 AM - 10:40 AM Review Class
Monday March 13th 10:40 AM - 12:00 PM Project Feedback
Wednesday March 15th 10:00 AM - 11:00 AM Project Feedback
Friday March 15th 10:00 AM - 11:00 AM Project Feedback
Week 11 (two weeks from now):
Monday March 20th 10:00 AM - 11:00 AM Class on Zoom (I’m away)
Monday March 20th 11:00 AM - 12:00 PM Student hours on Zoom (I’m away)
Wednesday March 22nd 10:00 AM - 11:00 AM Class on Zoom (I’m away)
Friday March 24th 10:00 AM - 11:00 AM Class is Cancelled
Week 12 (three weeks from now):
Return to regularly scheduled programming
Short demo on GitHub Issues and Project Feedback (5 mins)#
Class Outline#
Short demo on GitHub Issues and Project Feedback (5 mins)
Assert Statements (10 mins)
Try/Except clauses (10 mins)
Unit tests, corner cases (15 mins)
Multiple return values (15 mins)
Attribution#
The original version of these Python lectures were by Patrick Walls.
These lectures were delivered by Mike Gelbart and are available publicly here.
Assert statements#
test = ["1", "2", 3, 4, 5]
ans = [1, 2, 3, 4, 5]
assert (test == ans), "Helium baloons fly" # Left side isn't the same as the right side
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
Cell In[2], line 4
1 test = ["1", "2", 3, 4, 5]
2 ans = [1, 2, 3, 4, 5]
----> 4 assert (test == ans), "Helium baloons fly" # Left side isn't the same as the right side
AssertionError: Helium baloons fly
# method 1
for i, t in enumerate(test):
#print(i)
#print(t)
test[i] = int(t)
test
assert test == ans, "Failure! Left != Right"
# method 2
assert [int(t) for t in test] == ans, "Failure! Left != Right"
Difference between try/except
and assert
#
import numpy as np
def make_larger(n, denominator):
"""Makes the number larger"""
assert denominator != 0, "You provided 0 as the denominator - you are a terrible person"
assert (type(n) == int), "You did not provide an integer as an input"
ret = np.nan
# ret = n**2 / denominator
try:
ret = n**2 / denominator
except ZeroDivisionError as e:
print("You provided 0 as the denominator - you are a terrible person")
raise e
except TypeError as e:
print("You should make sure your inputs are actually numbers, I'll try to fix this")
ret = int(n)**2 / denominator
# except:
# print("there was an error in your code")
finally:
print("Don't worry, you are the best coder in the history of the coders")
return ret
num1 = "5"
num2 = 2
make_larger(num1, num2)
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
Input In [51], in <cell line: 4>()
1 num1 = "5"
2 num2 = 2
----> 4 make_larger(num1, num2)
Input In [50], in make_larger(n, denominator)
4 """Makes the number larger"""
6 assert denominator != 0, "You provided 0 as the denominator - you are a terrible person"
----> 7 assert (type(n) == int), "You did not provide an integer as an input"
9 ret = np.nan
10 # ret = n**2 / denominator
AssertionError: You did not provide an integer as an input
Try and except blocks (10 minutes)#
g = [1, 2, 3, 4, 5, 6, 7]
# g[8] = 'Hello' # This way will cause an error
# this is a more graceful way of dealing with errors
try:
g[4] = "Hello"
#h = 7 / 0
except IndexError:
print(
f"your list is less than 8 elements long, your list is actually {len(g)} elements"
)
# except ZeroDivisionError:
# print("stop doing stupid things")
# except:
# print('there is some sort of error in your code')
len(g)
7
Systematic Program Design#
A systematic approach to program design is a general set of steps to follow when writing programs. Our approach includes:
Write a stub: a function that does nothing but accept all input parameters and return the correct datatype.
Write tests to satisfy the design specifications.
Outline the program with pseudo-code.
Write code and test frequently.
Write documentation.
The key point: write tests BEFORE you write code.
You do not have to do this in MDS, but you may find it surprisingly helpful.
Often writing tests helps you think through what you are trying to accomplish.
It’s best to have that clear before you write the actual code.
Testing woes - false positives#
Just because all your tests pass, this does not mean your program is correct!!
This happens all the time. How to deal with it?
Write a lot of tests!
Don’t be overconfident, even after writing a lot of tests!
# Write a function to take in a list and return the median value
# Step 1
def sample_median2(x):
# Add code here
return median_value
# Step 2
def sample_median2(x):
# I'm going to sort the list
# I'm going to get the length of the list
# I'm going get the middle number of the length of the list (//)
# I'm going to access the median value
return median_value
# Step 3
def sample_median2(x):
# I'm going to sort the list
x_sorted = sorted(x)
# I'm going to get the length of the list
length = len(x)
# I'm going get the middle number of the length of the list (//)
middle = length // 2
# I'm going to access the median value
median_value = x_sorted[middle]
return median_value
# Step 4
def sample_median2(x):
assert type(x) == list, "You didn't pass me a list!"
# I'm going to sort the list
x_sorted = sorted(x)
# I'm going to get the length of the list
length = len(x)
# I'm going get the middle number of the length of the list (//)
middle = length // 2
# I'm going to access the median value
median_value = x_sorted[middle]
return median_value
# Step 5
def sample_median2(x):
assert type(x) == list, "You didn't pass me a list!"
# I'm going to sort the list
x_sorted = sorted(x)
# I'm going to get the length of the list
length = len(x)
# I'm going get the middle number of the length of the list (//)
middle = length // 2
# I'm going to access the median value
median_value = x_sorted[middle]
return median_value
assert sample_median2([1, 2, 3, 4, 5]) == 3
assert sample_median2([0, 0, 0, 0]) == 0
sample_median([5,4,2,1,3])
3
sample_median2(5)
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
Input In [65], in <cell line: 1>()
----> 1 sample_median2(5)
Input In [62], in sample_median2(x)
41 def sample_median2(x):
---> 43 assert type(x) == list, "You didn't pass me a list!"
45 # I'm going to sort the list
46 x_sorted = sorted(x)
AssertionError: You didn't pass me a list!
def sample_median(x):
"""Finds the median of a list of numbers."""
x_sorted = sorted(x)
return x_sorted[len(x_sorted) // 2]
assert sample_median([1, 2, 3, 4, 5]) == 3
assert sample_median([0, 0, 0, 0]) == 0
Looks good? … ?
assert sample_median([1, 2, 3, 4]) == 2.5
assert sample_median([1, 3, 2]) == 2
Testing woes - false negatives#
It can also happen, though more rarely, that your tests fail but your program is correct.
This means there is something wrong with your test.
For example, in the autograding for lab1 this happened to some people, because of tiny roundoff errors.
Corner cases#
A corner case is an input that is reasonable but a bit unusual, and may trip up your code.
For example, taking the median of an empty list, or a list with only one element.
Often it is desirable to add test cases to address corner cases.
assert sample_median([1]) == 1
In this case the code worked with no extra effort, but sometimes we need
if
statements to handle the weird cases.Sometimes we want the code to throw an error (e.g. median of an empty list); more on this later.
Multiple return values (5 mins)#
In most (all?) programming languages I’ve seen, functions can only return one thing.
That is technically true in Python, but there is a “workaround”, which is to return a tuple (or similarly, a dictionary).
# not good from a design perspective!
def sum_and_product(x, y):
return (x + y, x * y)
sum_and_product(5, 6)
In some cases in Python, the parentheses can be omitted:
def sum_and_product(x, y):
return x + y, x * y
sum_and_product(5, 6)
It is common to store these in separate variables, so it really feels like the function is returning multiple values:
s, p = sum_and_product(5, 6)
s
p
Question: is this good function design.
Answer: usually not, but sometimes.
You will encounter this in some Python packages.
That’s it for today!
See you on Thursday
Demo of writing code#
Task: GIve me a string, find the longest palindrome in the strong
Reverse a string
Find the largest number in a given list, if it’s even multiply it by 10, if it’s odd, multiply it by a 1000
def largest_num_operation(provided_list):
result = provided_list
return result
def largest_num_operation(provided_list):
result = provided_list
return result
assert (
largest_num_operation([1, 2, 3, 4, 5, 6]) == 60
), "Your function does not work for even numbers!"
assert (
largest_num_operation([1, 2, 3, 4, 5]) == 5000
), "Your function does not work for odd numbers!"
def largest_num_operation(provided_list):
result = provided_list
# 1. Find the largest number in the list and store it
# largest_val = max(provided_list)
# 2. I check to see if the number is odd or even
# %2 ==0 (even)
# %2 !=0 (odd)
# 3. If it's odd I multiply by 1000
# x 1000
# 4. If it's even, I multiply by 10
# x 10
return result
assert (
largest_num_operation([1, 2, 3, 4, 5, 6]) == 60
), "Your function does not work for even numbers!"
assert (
largest_num_operation([1, 2, 3, 4, 5]) == 5000
), "Your function does not work for odd numbers!"
def largest_num_operation(provided_list):
result = provided_list
# 1. Find the largest number in the list and store it
# largest_val = max(provided_list)
largest_val = max(provided_list)
# 2. I check to see if the number is odd or even
# %2 ==0 (even)
# %2 !=0 (odd)
if largest_val % 2 == 0:
# x 1000
elif largest_val:
# x 10
return result
# 3. If it's odd I multiply by 1000
# x 1000
# 4. If it's even, I multiply by 10
# x 10
return result
assert largest_num_operation([1,2,3,4,5,6]) == 60, "Your function does not work for even numbers!"
assert largest_num_operation([1,2,3,4,5]) == 5000, "Your function does not work for odd numbers!"
def largest_num_operation(provided_list):
largest_val = max(provided_list)
if largest_val == 0:
print("please put in a more useful number, instead of 0")
return
if largest_val % 2 == 0:
result = largest_val * 10
elif largest_val:
result = largest_val * 1000
return result
assert (
largest_num_operation([1, 2, 3, 4, 5, 6]) == 60
), "Your function does not work for even numbers!"
assert (
largest_num_operation([1, 2, 3, 4, 5]) == 5000
), "Your function does not work for odd numbers!"
largest_num_operation([-1, -2, -3, -4, -5, 0])
test_list = [1, 2, 3, 4, 5, 6]
max(test_list)
Write a stub: a function that does nothing but accept all input parameters and return the correct datatype.
Write tests to satisfy the design specifications.
Outline the program with pseudo-code.
Write code and test frequently.
Write documentation.