====== Computer Lab 09, debugging, code testing, exceptions ======
Error types:
- **Syntax error** (omitting colon at the end of the ''def'' statement, unclosed bracket, non-matching quotation marks, ...)
- **Runtime error** (division by zero, indexing error, using Python's keyword for a variable name, ...)
- **Semantic error** (error in the program's logic, hard to spot, interpreter nor runtime system is of no help here)
==== Syntax Error ====
def sum_numbers(lst)
sum = 0
for item in lst:
sum += item
return sum
File "syntax01.py", line 1
def sum_numbers(lst)
^
SyntaxError: invalid syntax
----
def add_gold(inv, inc_gold):
inv["gold"] += inc_gold
if __name__ == "__main__":
inventory = {
'gold' : 500,
'pouch' : ['flint', 'twine', 'gemstone'],
'backpack' : ['xylophone','dagger', 'bedroll','bread loaf']
add_gold(inventory, 50)
print(inventory)
File "syntax02.py", line 11
add_gold(inventory, 50)
^
SyntaxError: invalid syntax
----
mytuple = ("apple", "banana', "cherry")
for x in mytuple:
print(x)
File "syntax03.py", line 1
mytuple = ("apple", "banana', "cherry")
^
SyntaxError: invalid syntax
==== Runtime Error ====
* When a runtime error occurs an **exception object** is created.
* Then, traceback and an error message are printed out.
* If not handled, the program force ends.
**NAME ERROR**
if __name__ == "__main__":
value += 1
print("Value = ", value)
Traceback (most recent call last):
File "runtime01.py", line 2, in
value += 1
NameError: name 'value' is not defined
**TYPE ERROR (wrong number of parameters)**
def sum_numbers(a, b):
return a + b
if __name__ == "__main__":
nr_1 = 8
nr_2 = 10
nr_3 = -6
sum = sum_numbers(nr_1, nr_2, nr_3)
print("sum = ", sum)
Traceback (most recent call last):
File "runtime02.py", line 8, in
sum = sum_numbers(nr_1, nr_2, nr_3)
TypeError: sum_numbers() takes 2 positional arguments but 3 were given
**TYPE ERROR (division of strings)**
def div_numbers(a, b):
return a / b
if __name__ == "__main__":
nr_1 = input("Enter first number: ")
nr_2 = input("Enter second number: ")
sum = div_numbers(nr_1, nr_2)
print("Quotient = " + str(sum))
Enter first number: 10
Enter second number: 5
Traceback (most recent call last):
File "runtime02.py", line 7, in
sum = div_numbers(nr_1, nr_2)
File "runtime02.py", line 2, in div_numbers
return a / b
TypeError: unsupported operand type(s) for /: 'str' and 'str'
**TYPE ERROR (trying to concatenate //str// and //int//)**
def div_numbers(a, b):
return a / b
if __name__ == "__main__":
nr_1 = float(input("Enter first number: "))
nr_2 = float(input("Enter second number: "))
sum = div_numbers(nr_1, nr_2)
print("Quotient = " + sum)
Enter first number: 10
Enter second number: 5
Traceback (most recent call last):
File "runtime02.py", line 8, in
print("Quotient = " + sum)
TypeError: must be str, not float
**KEY ERROR (indexing dictionary with a non-existent key)**
fridge = {"apple": 10,
"banana": 8,
"cherry": 50}
print(fridge["kiwi"])
Traceback (most recent call last):
File "runtime03.py", line 5, in
print(fridge["kiwi"])
KeyError: 'kiwi'
**KEY ERROR (indexing dictionary with a wrong data type)**
fridge = {"apple": 10,
"banana": 8,
"cherry": 50}
print(fridge[0])
Traceback (most recent call last):
File "runtime03.py", line 5, in
print(fridge[0])
KeyError: 0
**INDEX ERROR (how to make this //pythonic//?)**
programming_languages = ["Java", "Python", "C++"]
count = 0
while count <= len(programming_languages):
print(programming_languages[count])
count += 1
Java
Python
C++
Traceback (most recent call last):
File "syntax04.py", line 5, in
print(programming_languages[count])
IndexError: list index out of range
**ZERO DIVISION ERROR**
import random
def get_inverted(x):
return 1/x
if __name__ == "__main__":
random.seed(128)
for _ in range(10):
value = random.randrange(0,10)
inv_value = get_inverted(value)
print("1/{} is {}.".format(value, inv_value))
1/3 is 0.3333333333333333.
1/6 is 0.16666666666666666.
1/8 is 0.125.
1/5 is 0.2.
1/2 is 0.5.
1/7 is 0.14285714285714285.
Traceback (most recent call last):
File "08_runtime.py", line 10, in
inv_value = get_inverted(value)
File "08_runtime.py", line 4, in get_inverted
return 1/x
ZeroDivisionError: division by zero
**FILE NOT FOUND ERROR**
filename = "dummyfile.txt"
f = open(filename, "r")
lines = f.readlines()
for line in lines:
print(line)
f.close()
Traceback (most recent call last):
File "runtime05.py", line 2, in
f = open(filename, "r")
FileNotFoundError: [Errno 2] No such file or directory: 'dummyfile.txt'
=== Handling Runtime Errors ===
* If not handled, the program stops after the exception is identified and an error message is printed out.
* Usually, the parts of the code which might cause an exception are easily identified.
* Use try-except to catch the exception and handle this situation yourself without stopping the program.
**FLAWED VERSION**
def div_numbers(a, b):
return a / b
if __name__ == "__main__":
nr_1 = input("Enter first number: ")
nr_2 = input("Enter second number: ")
sum = div_numbers(nr_1, nr_2)
print("Quotient = " + str(sum))
Enter first number: 10
Enter second number: 5
Traceback (most recent call last):
File "runtime02.py", line 7, in
sum = div_numbers(nr_1, nr_2)
File "runtime02.py", line 2, in div_numbers
return a / b
TypeError: unsupported operand type(s) for /: 'str' and 'str'
**ATTEMPT #1: TRY-EXCEPT in the function**
def div_numbers(a, b):
try:
q = a / b
return q
except TypeError:
return None
if __name__ == "__main__":
nr_1 = input("Enter first number: ")
nr_2 = input("Enter second number: ")
sum = div_numbers(nr_1, nr_2)
if sum is not None:
print("Quotient = " + str(sum))
else:
print("Error when calling the div_numbers() function.")
Enter first number: 10
Enter second number: 5
Error when calling the div_numbers() function.
**ATTEMPT #2: TRY-CATCH in the function + print(e)**
try:
q = a / b
return q
except TypeError as e:
print(e)
return None
TypeError: unsupported operand type(s) for /: 'str' and 'str'
**ATTEMPT #3: TRY-CATCH in the function + print traceback**
import traceback
def div_numbers(a, b):
try:
q = a / b
return q
except TypeError:
# printing stack trace
traceback.print_exc()
return None
Traceback (most recent call last):
File "handling01.py", line 5, in div_numbers
q = a / b
**ATTEMPT #4: SANITIZING INPUT DATA**
def div_numbers(a, b):
q = a / b
return q
if __name__ == "__main__":
try:
nr_1 = float(input("Enter first number: "))
nr_2 = float(input("Enter second number: "))
sum = div_numbers(nr_1, nr_2)
print("Quotient = " + str(sum))
except ValueError:
print("You did not enter valid numbers!")
**ATTEMPT #5: RAISING EXCEPTION**
def div_numbers(a, b):
if type(a) is not float or type(b) is not float:
raise TypeError("a or b is not float!")
q = a / b
return q
if __name__ == "__main__":
nr_1 = "a"
nr_2 = 5.0
try:
sum = div_numbers(nr_1, nr_2)
print("Quotient = " + str(sum))
except TypeError as e:
print(e)
a or b is not float!
==== Semantic Error ====
* Usually very difficult to spot!
* Mismatch between the problem specification and what the code actually does.
* Is there something the program was supposed to do but which doesn't seem to be happening?
* Is something happening that shouldn't?
* Several methods to find where the problem is:
- Create a set of well-defined inputs and corresponding outputs. Compare these ground-truth outputs with the actual outputs of your code. Based on this, try to identify the place where the error could originate.
- Place diagnostic print() statements in your code to inspect the content of your variables.
- Use the debugger to step through your program run by line. Set up (conditional) breakpoints.
- Write ''doctests'' (you already know) + ''unittest'' (will be explained once you learn about objects) to make ensure that the code does not break with changes.
==== Spot the Error ====
**EXERCISE 1**
def sum_even_numbers(lst):
sum = 0
for item in lst:
if item % 2 == 0:
sum += item
return sum
if __name__ == "__main__":
input_list = [9, 1, 0, 2, 8]
sum_of_list = sum_even_numbers(input_list)
print(sum_of_list)
**EXERCISE 2**
x = float(input('Enter a number: '))
y = float(input('Enter a number: '))
z = x+y/2
print ('The average of the two numbers you have entered is:',z)
Enter a number: 3
Enter a number: 4
The average of the two numbers you have entered is: 5.0
**EXERCISE 3**
# A function to append a list onto itself, with the intention of
# returning a new list, but leaving the input unaltered
def double_list(in_list):
"""Append a list to itself."""
in_list += in_list
return in_list
# Make a list
my_list = [3, 2, 1]
# Double it
my_list_double = double_list(my_list)
# Later on in our program, we want a sorted my_list
my_list.sort()
# Let's look at my_list:
print('We expect [1, 2, 3]')
print('We get ', my_list)
=== Weekly homework 09 - Piecewise function ===
In this homework, you will need to write 2 functions in one module (file):
* Function ''my_pwc_function'' implementing a piecewise function defined by the following formula:
\[ f(x)= \begin{cases}
(x-6)^2+5 & 6 \le x \\
5 & 4 \leq x\lt 6 \\
\frac{5}{2}x-5 & 2 \leq x\lt 4 \\
\text{undefined} & x \lt 2
\end{cases}
\]
def my_pwc_function(x):
"""Returns a function value of the piecewise function specified in the problem setting
:param x: float, parameter of the mathematical function
:return: float, corresponding function value
:raises: ValueError: for such 'x' where the piecewise function is undefined
>>> my_pwc_function(5)
5.0
>>> my_pwc_function(3)
2.5
"""
* Function ''get_function_values(par_lst)'' which computes function values (using ''my_pwc_function()'') of all numbers given in the ''par_lst'' list and stores all the results in a dictionary. In case of an undefined function value, place 'NA' string as a value to the corresponding key.
def get_function_values(par_lst):
"""Returns dictionary where the key are numbers from par_list and values are corresponding
function values.
:param par_lst: list of floats, values to be passed to the piecewise function
:return: dict of x:my_pwc_function(x), function values
>>> get_fuction_values([5, 1, 3])
{5:5.0, 1:'NA', 3:2.5}
"""
Required filename: ''09_weekly_hw.py''.