====== 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_function_values([5, 1, 3]) {5:5.0, 1:'NA', 3:2.5} """ Required filename: ''09_weekly_hw.py''.