10. Exceptions

10.1. What are and why to use exceptions?

  • Used when error occurs

  • You can catch exception and handles erroneous situation

  • Exception example situations:

    • File does not exists
    • Function argument is invalid
    • Network or database connection could not be established

10.2. Most common exceptions

10.2.1. AttributeError

  • Attribute reference or assignment fails
Code Listing 10.2. AttributeError exception
name = 'Jose'

name.append('Jimenez')
# AttributeError: 'str' object has no attribute 'append'

10.2.2. ImportError, ModuleNotFoundError

  • Module could not be located
Code Listing 10.3. ModuleNotFoundError exception
import math
import match
# ModuleNotFoundError

10.2.3. IndexError

  • Sequence subscript is out of range
Code Listing 10.4. IndexError exception
DATA = ['a', 'b', 'c']

DATA[100]
# IndexError: list index out of range

10.2.4. KeyError

  • Dictionary key is not found
Code Listing 10.5. KeyError exception
DATA = {'a': 1, 'b': 2}

DATA['x']
# KeyError: 'x'

10.2.5. NameError

  • Local or global name is not found
Code Listing 10.6. KeyError exception
print(first_name)
# NameError: name 'first_name' is not defined

10.2.6. SyntaxError

  • Parser encounters a syntax error
Code Listing 10.7. SyntaxError exception
if True
    print('Yes')

# SyntaxError: invalid syntax

10.2.7. IndentationError

  • Syntax errors related to incorrect indentation
Code Listing 10.8. IndentationError exception
if True:
   print('Hello!')
    print('My name...')
   print('Jose Jimenez')

# IndentationError: unexpected indent

10.2.8. TypeError

  • Operation or function is applied to an object of inappropriate type
Code Listing 10.9. TypeError exception
42 + 'Jose'
# TypeError: unsupported operand type(s) for +: 'int' and 'str'

10.2.9. ValueError

  • Argument is right type but an inappropriate value
Code Listing 10.10. ValueError exception
float('hello')
# ValueError: could not convert string to float: 'hello'

10.3. Raising exceptions

10.3.1. Raise Exception without message

Code Listing 10.11. Raise Exception without message
raise RuntimeError

10.3.2. Exception with additional message

Code Listing 10.12. Exception with additional message
raise RuntimeError('Some message')

10.3.3. Use case

def apollo13():
    raise RuntimeError('Mid-flight Oxygen tank explosion')


apollo13()
def apollo18():
    raise NotImplementedError('Mission dropped due to budget cuts')


apollo18()

10.4. Traceback

10.4.1. Traceback analysis

  • Stacktrace is 8 levels deep, it’s not Java’s 200 ;)
def apollo13():
    raise RuntimeError('Mid-flight Oxygen tank explosion')

apollo13()
# Traceback (most recent call last):
#   File "/Users/matt/.virtualenvs/book-python/lib/python3.7/site-packages/IPython/core/interactiveshell.py", line 2961, in run_code
#     exec(code_obj, self.user_global_ns, self.user_ns)
#   File "<ipython-input-2-badb71482ca2>", line 1, in <module>
#     runfile('/Users/matt/Developer/book-python/__notepad__.py', wdir='/Users/matt/Developer/book-python')
#   File "/Applications/PyCharm 2018.3 EAP.app/Contents/helpers/pydev/_pydev_bundle/pydev_umd.py", line 198, in runfile
#     pydev_imports.execfile(filename, global_vars, local_vars)  # execute the script
#   File "/Applications/PyCharm 2018.3 EAP.app/Contents/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
#     exec(compile(contents+"\n", file, 'exec'), glob, loc)
#   File "/Users/matt/Developer/book-python/__notepad__.py", line 13, in <module>
#     apollo13()
#   File "/Users/matt/Developer/book-python/__notepad__.py", line 5, in apollo13
#     raise RuntimeError('Mid-flight Oxygen tank explosion')
# RuntimeError: Mid-flight Oxygen tank explosion

10.4.2. Change verbosity level

  • Change level with sys.tracebacklimit
  • From time to time you can have problems somewhere in the middle, but it’s rare
  • Last lines are the most important, in most cases error is there
import sys

sys.tracebacklimit = 1


def apollo13():
    raise RuntimeError('Mid-flight Oxygen tank explosion')

apollo13()
# Traceback (most recent call last):
#   File "/Users/matt/Developer/book-python/__notepad__.py", line 5, in apollo13
#     raise RuntimeError('Mid-flight Oxygen tank explosion')
# RuntimeError: Mid-flight Oxygen tank explosion

10.5. Catching exceptions

  • try
  • except
  • else
  • finally

10.5.1. Catch single exception

def apollo13():
    raise RuntimeError('Mid-flight Oxygen tank explosion')


try:
    apollo13()
except RuntimeError:
    print('Houston we have a problem!')

10.5.2. Catch many exceptions with the same handling

def apollo13():
    raise RuntimeError('Mid-flight Oxygen tank explosion')


try:
    apollo13()
except (RuntimeError, TypeError, NameError):
    print('Houston we have a problem!')

10.5.3. Catch many exceptions with different handling

def open_file(path):
    if path.startswith('/tmp/'):
        print('Will make file')
    elif path.startswith('/etc/'):
        raise PermissionError('Permission Denied')
    else:
        raise FileNotFoundError('File not found')


try:
    open_file('/etc/my-file.txt')
except FileNotFoundError:
    print('File not found')
except PermissionError:
    print('Permission Denied')

10.5.4. Exceptions logging

import logging

def open_file(filename):
    raise PermissionError('Permission Denied')


try:
    open_file('/tmp/my-file.txt')
except PermissionError as err:
    logging.error(err)
def apollo11():
    print('Program P63 - Landing Manoeuvre Approach Phase')
    raise RuntimeError('1201 Alarm')
    raise RuntimeError('1202 Alarm')
    print('Contact lights')
    print('The Eagle has landed!')
    print("That's one small step for [a] man, one giant leap for mankind.")


try:
    apollo11()

except RuntimeError:
    print("Yo're GO for landing")

except Exception:
    print('Abort')

else:
    print('Landing a man on the Moon')

finally:
    print('Returning safely to the Earth')

10.5.5. Always catch exceptions!

# Problematic code which catches 'Ctrl-C'
# User cannot simply kill program
while True:
    try:
        number = float(input('Type number: '))
    except:
        continue
# User can kill program with 'Ctrl-C'
while True:
    try:
        number = float(input('Type number: '))
    except Exception:
        continue

10.6. Exception hierarchy

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StopAsyncIteration
      +-- ArithmeticError
      |    +-- FloatingPointError
      |    +-- OverflowError
      |    +-- ZeroDivisionError
      +-- AssertionError
      +-- AttributeError
      +-- BufferError
      +-- EOFError
      +-- ImportError
      +-- LookupError
      |    +-- IndexError
      |    +-- KeyError
      +-- MemoryError
      +-- NameError
      |    +-- UnboundLocalError
      +-- OSError
      |    +-- BlockingIOError
      |    +-- ChildProcessError
      |    +-- ConnectionError
      |    |    +-- BrokenPipeError
      |    |    +-- ConnectionAbortedError
      |    |    +-- ConnectionRefusedError
      |    |    +-- ConnectionResetError
      |    +-- FileExistsError
      |    +-- FileNotFoundError
      |    +-- InterruptedError
      |    +-- IsADirectoryError
      |    +-- NotADirectoryError
      |    +-- PermissionError
      |    +-- ProcessLookupError
      |    +-- TimeoutError
      +-- ReferenceError
      +-- RuntimeError
      |    +-- NotImplementedError
      |    +-- RecursionError
      +-- SyntaxError
      |    +-- IndentationError
      |         +-- TabError
      +-- SystemError
      +-- TypeError
      +-- ValueError
      |    +-- UnicodeError
      |         +-- UnicodeDecodeError
      |         +-- UnicodeEncodeError
      |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- ResourceWarning

10.7. Defining own exceptions

import math


class CotangentDoesNotExistsError(Exception):
    pass


def cotangent(degrees):
    if degrees == 180:
        raise CotangentDoesNotExistsError('Cotangent for 180 degrees is infinite')

    radians = math.radians(degrees)
    return 1 / math.tan(radians)


cotangent(180)
# CotangentDoesNotExistsError: Cotangent for 180 degrees is infinite

10.8. Real life use-case

from django.contrib.auth.models import User


username = POST.get('username')
password = POST.get('password')

try:
    User.objects.get(username=username, password=password)
except User.DoesNotExists:
    print('Permission denied')