This site is from a past semester! The current version will be here when the new semester starts.

Error Handling

Introduction to Errors

In Python, there are (at least) two kinds of errors: syntax errors and exceptions.


Syntax Errors

A syntax error, also known as a parsing error, is when your code does not follow rules for writing Python code. Python interpreter shows an error message when it encounters a syntax error.

The code below has a syntax error because it breaks the Python rule that requires a : to follow the condition of an if statement.

if 5 > 4
  print('Greater')
 → 

Traceback (most recent call last):
  File "python", line 1
    if 5 > 4
           ^
SyntaxError: invalid syntax

Some Python editors (e.g., repl.it) flag syntax errors even before you run the code.

Note how replt.it Python editor points out the syntax error using a red ❌ in the of the editor.

Handling Exceptions

Errors detected during execution are called exceptions. Even if the code is syntactically correct, it may cause an error during execution. Python different types of exceptions; the type of exception used depends on the tye nature of the error.

The code below raises an exception when it attempts to divide a number by 0. The type of the exception raised is ZeroDivisionError, as mentioned in the last line of the error message.

def divide(number, divisor):
  print('Starting calculation')
  result = number/divisor
  print(number, '/', divisor, '=', result)
  print('Calculation over!........')

divide(50, 5)
divide(3, 0)
 → 

Starting calculation
50 / 5 = 10.0
Calculation over!........

Starting calculation
Traceback (most recent call last):
  File "python", line 9, in <module>
  File "python", line 4, in divide
ZeroDivisionError: division by zero

It is not desirable for programs to 'crash' every time an exception occurs. You can use the try-except syntax to specify how to handle exceptions. The try clause contains the code that can possibly raise an exception while the except clause contains the code that handles the exception.

The code below specifies what to do if the ZeroDivisionError is raised, thereby avoiding a program crash in such an event.

def divide2(number, divisor):
  print('Starting calculation')
  try:
    result = number/divisor
    print(number, '/', divisor, '=', result)
  except ZeroDivisionError:
    print('Cannot divide by zero')
  print('Calculation over!........')

divide2(3, 0)
divide2(3, 1.5)
 → 

[visualize]

Starting calculation
Cannot divide by zero
Calculation over!........

Starting calculation
3 / 1.5 = 2.0
Calculation over!........

When the code in a try clause raises an error, the program execution immediately moves to the code in the except clause, provided the exception that happened matches the exception the except clause is supposed to . After running the code in the except clause, the execution continues as normal.

If the exception does not match the except clause, the program crashes.

The code below crashes because the actual exception (caused by passing a string when an floating point number is expected) does not match the specified exception ZeroDivisionError.

divide2(5, 'abc')
 → 

Starting calculation
Traceback (most recent call last):
  File "python", line 24, in <module>
  File "python", line 16, in divide2
TypeError: unsupported operand type(s) for /: 'int' and 'str'

You can specify multiple except clauses, one for each type of exception expected.

The code below handles two types of exceptions: ZeroDivisionError and ValueError.

The ValueError is raised when the string abc is being converted to a float using float(divisor).

def divide3(number, divisor):
  print('Calculating', number, '/', divisor)
  try:
    result = float(number)/float(divisor)
    print(number, '/', divisor, '=', result)
  except ZeroDivisionError:
    print('Cannot divide by zero')
  except ValueError:
    print('Cannot divide non-numbers')
  print('Calculation over!........')

divide3(3, 0)
divide3(3, 'abc')
 → 

Calculating 3 / 0
Cannot divide by zero
Calculation over!........

Calculating 3 / abc
Cannot divide non-numbers
Calculation over!........

It is possible to specify multiple exception types in one except clause.

The code below handles both ZeroDivisionError and ValueError in the same except clause.

def divide4(number, divisor):
  print('Calculating', number, '/', divisor)
  try:
    result = float(number)/float(divisor)
    print(number, '/', divisor, '=', result)
  except (ZeroDivisionError, ValueError):
    print('Incorrect inputs')
  print('Calculation over!........')

divide4(3, 0)
divide4(3, 'abc')
 → 

Calculating 3 / 0
Incorrect inputs
Calculation over!........

Calculating 3 / abc
Incorrect inputs
Calculation over!........

IndexError is an exception thrown when you try to access an index (e.g., when reading values form a list) that does not exist.

In this example, calling get_head on an empty list causes an IndexError.

def get_head(items):
  try:
    return items[0]
  except IndexError:
    return 'List too short'

print(get_head([]))
 → 

List too short

It is also possible to use except Exception to catch any kind of exception. However, that practice is discouraged.

The code below handles both ZeroDivisionError and ValueError and any other exception in the same except clause.

def get_head2(items):
  try:
    return items[0]
  except Exception:
    return 'Something wrong'

print(get_head2(2))
 → 

Something wrong

📎 Resources:

Exercise: Enter Integer

Exercise : Enter Integer

The code below assumes the entered string will always represent an integer.

value = input('Input an integer: ')
print('You entered', value)

However, users may not follow that assumption. If the user were to enter a non-integer string, the program crashes. To avoid such crashes, programs need to do input validation i.e., perform checks on inputs to ensure their validity.

Update the above code so that it accepts integers only, and produces the behavior given below.

Input an integer: x
That is not an integer, try again
Input an integer: 5.5
That is not an integer, try again
Input an integer: 6
You entered 6

💡 Hint


Partial solution



Raising Exceptions

You can raise an exception yourself to indicate an error.

The get_body(items) function below raises an exception when it receives a list that has fewer than 3 items. That exception is 'caught' and handled by the hide_ends(items) function.
Also note how an except clause can assign a name to the exception using as temporary_name (as done by except ValueError as e:) so that the exception object can be referenced later (as done in print('Cannot hide ends', str(e)))

def get_body(items):
  if len(items) < 3:
    raise ValueError('Not enough items')

  return items[1:-1]

def hide_ends(items):
  try:
    body = get_body(items)
    print(['_'] + body + ['_'])
  except ValueError as e:
    print('Cannot hide ends:', str(e))

hide_ends([0, 1])
hide_ends([0, 1, 2, 3, 4])
 → 

[visualize]

Cannot hide ends: Not enough items
['_', 1, 2, 3, '_']

It is also possible to catch an exception, do something, and then raise it again so that the exception propagates to the caller.

The code hide_ends2(items) function below catches the ValueError exception, prints an error message, and raises it again so that the code that called the function can catch the exception again. Also note how the line hide_ends2([0, 1, 2, 3, 4]) is never executed due to the exception raised by the line just above it.

def hide_ends2(items):
  try:
    body = get_body(items)
    print(['_'] + body + ['_'])
  except ValueError as e:
    print('Cannot hide ends:', str(e))
    raise

try:
  hide_ends2([0])
  hide_ends2([0, 1, 2, 3, 4])
except ValueError as e:
  print('hide_ends2 failed:', str(e))
 → 

[visualize]

Cannot hide ends: Not enough items
hide_ends2 failed: Not enough items

Here are some commonly used built-in exceptions (full list) you can raise/handle in your code:

  • IndexError: Indicates an index of a sequence (e.g., a list) is out of range.
  • RuntimeError : Indicates an error that does not fall under any other category.
  • ValueError: Indicates when a function gets argument of correct type but improper value.
  • ZeroDivisionError : Indicates an attempt to divide by zero.

It is also possible to define your own user-defined exceptions but that requires more advanced programming techniques. It will be covered elsewhere.

Exercise: Is Even-Integer in Range

Exercise : Is Even-Integer in Range

This exercise has a long description because it explains how the given code is structured. It is important for you to learn how to break down code into smaller functions like the ones given in this exercise.

The function given below checks if a given input is an even integer in a given range.

def check(number, start, end):
  print(number, 'is an int in range', start, '-', end, '?', is_even_int_in_range(number, start, end))

Some example inputs and outputs are given below:

check(3, 'y', 'z') # False (3 is not even)
check(2, 3.4, 5)
check(2, 3, [])
check(2, 5, 1)
check(2, 5, 5)
check(3, 1, 5)
check(4, 1, 4) # False ( range 1 to 4 excludes 4)
check(4, 1, 5)

x is an int in range y - z ? Value error: x is not an integer
3 is an int in range y - z ? No
2 is an int in range 3.4 - 5 ? Value error: 3.4 is not an integer
2 is an int in range 3 - [] ? Value error: [] is not an integer
2 is an int in range 5 - 1 ? Value error: end is smaller than start
2 is an int in range 5 - 5 ? No
3 is an int in range 1 - 5 ? No
4 is an int in range 1 - 4 ? No
4 is an int in range 1 - 5 ? Yes

Note how the check function uses an is_even_int_in_range functions whose code and the behavior are given below.

is_even_int_in_range(number, start, end):

  • Returns 'Yes' if number is an even integer in the range start to end. Returns 'No' otherwise.
  • Returns an error message if any of the inputs are incorrect.
  • Code:
    def is_even_int_in_range(number, start, end):
      try:
        if is_even_int(number) and is_in_range(number, start, end):
          return 'Yes'
        else:
          return 'No'
      except ValueError as e:
        return 'Value error: ' + str(e)
    

Note how the above function uses two other functions is_even_int and is_in_range given below:

is_even_int(number):

  • Returns True if the number is an even integer. False otherwise.
  • Raises a ValueError if the number is not an integer.
  • Code:
    def is_even_int(number):
      confirm_is_int(number)
      return int(number)%2 == 0
    

is_in_range(number, start, end):

  • Returns True if the number is in the range start to end (including start but excluding end, as per how Python define ranges). False otherwise.
  • Raises a ValueError if the number is not an integer or start and end do not specify a range correctly.
  • Code:
    def is_in_range(number, start, end):
      confirm_is_int(number)
      confirm_range_correct(start, end)
      return number >= start and number < end
    

Note how the above two functions use two other functions confirm_is_int and confirm_range_correct. Their expected behavior and partial code is given below. Your job is to complete those two functions.

confirm_is_int(number):

  • Raises a ValueError if the number is not an integer.
  • Partial code:
    def confirm_is_int(number):
      pass # ADD YOUR CODE HERE!
    
  • You can use the type(value) is type_name and type(value) is not type_name to check if a value is of type type_name
    e.g., type('x') is not float evaluates to True because 'x' is not a float.
    [more examples ...]

confirm_range_correct(start, end):

  • Raises a ValueError if end or start are not integers. This behavior is already implemented by the code given below, using two calls to the confirm_is_int function.
  • Raises a ValueError('end is smaller than start') if end is smaller than start. You need to implement this behavior.
  • Partial code:
    def confirm_range_correct(start, end):
      confirm_is_int(start)
      confirm_is_int(end)
      # ADD YOUR CODE HERE!
    

Partial solution



Exercise: Flexible Word Game

Exercise : Flexible Word Game

Implement a word game similar to the one you implement in the previous Word Game Exercise, but with the following difference:

  • The player is asked to choose the word size. The word size can only be 4 to 8 (both inclusive). If the user entry is not an integer or not in the range 4..8, the program keeps asking for the word size.

Given below is a sample session. Try to follow the exact output format in your implementation:

========================================================
Welcome to the FLEXIBLE WORD GAME
Enter the word size (4 to 8): xyz
That is not a number. Try again
Enter the word size (4 to 8): 3
Number not in correct range. Try again
Enter the word size (4 to 8): 5
Give all 5-letter words you know, one word at a time
Enter the word 'end' to exit
========================================================
What's the next word?  frame
What's the next word?  great
What's the next word?  treat
What's the next word?  fat
Not a 5-letter word
What's the next word?  creamy
Not a 5-letter word
What's the next word?  treat
Repeated word! treat is number 3 in the accepted words list.
treat is no longer an accepted word and is banned
What's the next word?  crime
What's the next word?  treat
treat is banned!
What's the next word?  end
========================================================
Your score: 3
Accepted words (in order of entry): frame great crime
Accepted words (in sorted order): crime frame great
Banned words (in sorted order): treat
Thank you for playing the FLEXIBLE WORD GAME
========================================================