Python videos

Exceptions: error handling

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 (<string>, line 2)

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

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 preferred!
  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.

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

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

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.

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

Exercises

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

View solution