Lab 8
Note: This is intentionally a shorter lab. If you get through all the exercises, you are encouraged to spend some time catching up on any exercises you missed in previous labs, or working on the assignment.
If you have any questions about or difficulties with any of the material covered in the course so far, ask your tutor for help during the lab.
Objectives#
The purpose of this week’s lab is to:
- Explore python’s exceptions and exception handling mechanism.
- Think about code quality - particularly with respect to function docstrings and variable names.
Exceptions#
Exceptions are python’s runtime error mechanism. The assert
and
raise
statements allow the programmer to generate exceptions when
error conditions occur, while the try
statement allows us to
implement an exception handler which will be activated if an
exception occurs.
When a runtime error occurs, an exception (of a suitable type) is
raised. If code in which the exception was raised is within the
suite of a try
statement that has an except
clause for that
type of exception, the suite of the except
clause (that is, the
exception handler) is executed; we say the exception has been caught,
and execution continues with the next statement after the try
statement.
If this is not the case, and the exception was raised in the execution
of a funciton, the exception is returned to point of the function call,
and the same procedure repeated with the call treated as the origin of
the exception. In this way, an exception travels up a call chain until
it is either caught by an appropriate handler, or it reaches the top
level, in which case the python interpreter stops execution and prints
an error message.
Exercise 0#
Copy the following two function definitions to a file and run them:
def funA(a, i):
try:
return a[i] + i
except TypeError:
print("Caught TypeError in funA")
except IndexError:
print("Caught IndexError in funA")
return 0
def funB(a, i):
try:
i = funA(a, i)
return a[i]
except TypeError:
print("Caught TypeError in funB")
except IndexError:
print("Caught IndexError in funB")
return 0
- What happens if you call
funB([3,2,1], 3)
? - What happens if you call
funB([3,2,1], 0)
? - Can you find arguments to
funB
that cause theTypeError
handler infunA
to be executed? - Can you find arguments to
funB
that cause theTypeError
handler infunB
to be executed? - Can you find arguments to
funB
that cause an exception to be raised that is not caught by any of the exception handlers?
Exercise 1#
(a) Consider the following function (from the lecture on algorithm complexity):
def sorted_find(x, slist):
if slist[-1] <= x:
return slist[-1]
lower = 0
upper = len(slist) - 1
while (upper - lower) > 1:
middle = (lower + upper) // 2
if slist[middle] <= x:
lower = middle
else:
upper = middle
return slist[lower]
The intent of the function is to return the greatest element that is
less than or equal to x
; it assumes that slist
is a sorted sequence.
- What are all the runtime errors that may be raised in this function?
- Are there any further, unstated assumptions? That is, are there any cases in which the function will not raise an exception, but return an incorrect answer? If so, is there assertion that can be added to the function to ensure it raises an exception instead of failing silently?
(b) Pick a function that you have written as a solution to any exercise from any of the previous labs, and find the answers to the two questions above for that function.
Exercise 2#
Recall that python defines ordering of sequences so that a sequence
S1 < S2
is True iff there is an index i
such that S1[i] < S2[i]
and S1[j] == S2[j]
for indices j
from 0 to i
.
This definition of ordering does not work for sets, because sets are
not ordered, and not indexable (that is, we cannot take the i:th
element of a set).
Let’s define an ordering on sets as follows: set A
is less than set
B
if for every element a
in A
there exists some element b
in B
such that a < b
is True. Recall that values of different types are not
always comparable; if a
and b
are two values that cannot be compared
(for example, if a
is a string and b
a number), a < b
is considered
to be False.
Write a function set_less_than(A, B)
, that takes two sets A
and
B
and returns True if A
is less than B
according to the above
definition, and False otherwise.
Examples
{ 0.5, 1.5, 2.5 }
is less than{ 1, 2, 3 }
{ 'a', 'b' }
is less than{ 'ab', 'bb' }
{ 0, 'a', (0, 'a') }
is less than{ 1, 'c', (2, -3) }
Hint: The easiest way to determine if two elements are comparable
is to try comparing them. If comparison is not possible, it will raise
a TypeError
.
Excercise 3#
We saw in the lecture that one purpose of a function docstring is to specify the type and purpose of each argument and the type of the return value. This can be done by including a statement such as:
:param param_name: purpose, type.
:return return type: purpose.
For each of the functions for exercise 2 and 3, go back and write a formal docstring for the function. Include a short description of what the function does and then for each parameter write a full description of its purpose and allowable types(s).
Then modify your functions to include assert
statements to check that each
of the input parameters are actually of the required type(s). If they are not,
your assert statements should print a suitable and informative error message.
You can test the type
of a variable by writing:
type(my_variable)
Finally, for each variable that is used in either function, have a think about the name you have chosen for the variable. Does it clearly indicate the purpose of the variable? Is it too similar to another variable name? Ask the person sitting next to you to have a look at your code. Can they explain to you how it works? If they can’t easily see what’s going on, think about how you could re-organise it, rename variables or add comments to make it clearer.