OpenSesame
Rapunzel Code Editor
DataMatrix
Support forum
Python Tutorials
MindProbe
Python videos

Exceptions: error handling

This tutorial contains three interactive mini exercises and one review exercise. Try to solve them all!

SyntaxErrors v Exceptions

A SyntaxError occurs when you try to run syntactically invalid Python code, that is, when you're asking Python to execute code that is not actually Python code. When this happens, the code is not executed at all, not even those lines that are syntactically valid. This is very different from an Exception!

if x = 0: # = should be ==
  print('x is 0')

Output:

SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='? (<string>, line 3)

An Exception occurs when there is an error during execution of syntactically valid Python code. In Python terminology, an Exception is raised. To illustrate this, let's define an unsafe function, that is, a function that can easily result in an Exception.

def oneover(i):
  return 1 / i

Calling oneover(0) results in a ZeroDivisionError (a special kind of Exception) because dividing any number by 0 is not allowed:

oneover(0)

Output:

ZeroDivisionError: division by zero

Mini exercise

A ValueError is a special kind of Exception that is raised when a function is called with an argument that has the correct type but an incorrect value. Trigger a ValueError by trying to convert the string 'this is not an int' to an int.

Handling Exceptions

try … except …

Now let's use a try … except … statement to safely catch Exceptions.

try:
  i = oneover(0)
except:  # A blank except is not good practice!
  print('Some problem occurred')
print("I'm still alive!")

Output:

Some problem occurred
I'm still alive!

In the example above, if any Exception occurs in the block that follows the try statement, then the execution of that block is terminated, and the except block is executed. Importantly, however, the code continues to run; that is, try … except … statements allow you to deal with Exceptions gracefully.

It is good practice to specify which Exceptions should be caught. For example, oneover() triggers a ZeroDivisionError error when called with 0 and a TypeError called it with a str or some other value that doesn't work in a numeric division. Therefore, we can specify that we want to catch only those two Exceptions, and in addition specify that we want to keep the Exception object as the variable e. Restricting exception handling in this way avoids masking of errors that we did not anticipate, and which may reflects bugs in our code.

try:
  i = oneover(0)
except (TypeError, ZeroDivisionError) as e:
  print('A problem occurred: %s' % e)

Output:

A problem occurred: division by zero

You can also handle specify a different way to handle each different kind of Exception by having multiple except blocks:

try:
  i = oneover(0)
except TypeError:
  # This will be executed when a TypeError is raised
  print('oneover() expects a float or int')
except ZeroDivisionError:
  # This will be executed when a ZeroDivisionError is raised
  print('oneover() cannot be called with 0')

Output:

oneover() cannot be called with 0

Re-raising (from)

We can also pass the Exception on after catching it, by doing a blank raise.

try:
  i = oneover(0)
except ZeroDivisionError as e:
  print('Oops!')
  raise

Output:

Oops!
ZeroDivisionError: division by zero

Or you can do a raise … from (Python 3 only).

try:
    i = oneover(0)
except ZeroDivisionError as e:
    raise ValueError('Cannot divide by zero') from e

Output:

ValueError: Cannot divide by zero

else … finally …

The else block of a try … except … is executed when no Exception occurred during the try block. And finally there is a finally block, which is always executed, regardless of whether or not an Exception occurred; this can be used to perform clean-up operations etc.

try:
    i = oneover('x')
except ZeroDivisionError as e:
    print('Cannot divide by zero')
except TypeError as e:
    print('Expecting a non-zero number')
else:
    print('No exception occurred')
finally:
    print('This is always executed')

Output:

Expecting a non-zero number
This is always executed

Mini exercise

Create a function called safe_int() that takes a single argument i. If possible, the function converts i to int and returns it. If not possible (i.e. if an Exception occurs), the function returns None.

Raising Exceptions

You can raise Exceptions yourself to indicate that something went wrong. It is good practice to use Python's built-in Exception objects whenever this makes sense.

def factorial(n):

    if n < 0:
        raise ValueError('Factorial expects non-negative integers')
    return 1 if n == 0 else n*factorial(n-1)


factorial(-1)

Output:

ValueError: Factorial expects non-negative integers

But you can also create custom Exception objects. This allows you to communicate clearly to the user what kind of error occurred.

class FactorialError(Exception): pass


def factorial(n):

    if n < 0:
        raise FactorialError('Factorial expects non-negative integers')
    return 1 if n == 0 else n*factorial(n-1)


factorial(-1)

Output:

FactorialError: Factorial expects non-negative integers

Mini exercise

Define a function capitalize_last_name() that accepts as argument a string with a (single) first and a (single) last name, and returns a string in which only the first letter of the first name is uppercase, whereas all letters of the last name are uppercase; in otherwords, 'marisa tomei' becomes 'Marisa TOMEI'. (Tip: use str.split() to split a str into separate words.)

If something other than a str object is passed as an argument, the function should raise a TypeError. (Tip: you can use isistance() to check whether an object is of a particular type.) If the str does not consist of exactly two words, the function should raise a ValueError.

Review exercise

An interactive calculator

You're going to write an interactive calculator! User input is assumed to be a formula that consist of a number, an operator (at least + and -), and another number, separated by white space (e.g. 1 + 1). Split user input using str.split(), and check whether the resulting list is valid:

  • If the input does not consist of 3 elements, raise a FormulaError, which is a custom Exception.
  • Try to convert the first and third input to a float (like so: float_value = float(str_value)). Catch any ValueError that occurs, and instead raise a FormulaError
  • If the second input is not '+' or '-', again raise a FormulaError

If the input is valid, perform the calculation and print out the result. The user is then prompted to provide new input, and so on, until the user enters quit.

An interaction could look like this:

>>> 1 + 1
2.0
>>> 3.2 - 1.5
1.7000000000000002
>>> quit

This exercise is not checked automatically, because there are several possible solutions. Click here to see one solution!

This concludes the Python Basics course. Congratulations—you made it to the finish!