8. Exceptions

8.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

8.2. Most common exceptions

8.2.1. AttributeError

  • Attribute reference or assignment fails

Listing 67. AttributeError exception
name = 'Jose'

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

8.2.2. ImportError, ModuleNotFoundError

  • Module could not be located

Listing 68. ModuleNotFoundError exception
import math
import match
# ModuleNotFoundError

8.2.3. IndexError

  • Sequence subscript is out of range

Listing 69. IndexError exception
DATA = ['a', 'b', 'c']

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

8.2.4. KeyError

  • Dictionary key is not found

Listing 70. KeyError exception
DATA = {'a': 1, 'b': 2}

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

8.2.5. NameError

  • Local or global name is not found

Listing 71. KeyError exception
print(first_name)
# NameError: name 'first_name' is not defined

8.2.6. SyntaxError

  • Parser encounters a syntax error

Listing 72. SyntaxError exception
if True
    print('Yes')

# SyntaxError: invalid syntax

8.2.7. IndentationError

  • Syntax errors related to incorrect indentation

Listing 73. IndentationError exception
if True:
   print('Hello!')
    print('My name...')
   print('Jose Jimenez')

# IndentationError: unexpected indent

8.2.8. TypeError

  • Operation or function is applied to an object of inappropriate type

Listing 74. TypeError exception
42 + 'Jose'
# TypeError: unsupported operand type(s) for +: 'int' and 'str'

8.2.9. ValueError

  • Argument is right type but an inappropriate value

Listing 75. ValueError exception
float('hello')
# ValueError: could not convert string to float: 'hello'

8.3. Raising exceptions

8.3.1. Raise Exception without message

Listing 76. Raise Exception without message
raise RuntimeError

8.3.2. Exception with additional message

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

8.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()

8.4. Traceback

8.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

8.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

8.5. Catching exceptions

  • try

  • except

  • else

  • finally

8.5.1. Catch single exception

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


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

8.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!')

8.5.3. Catch many exceptions with different handling

try:
    with open(r'/tmp/iris.csv') as file:
        content = file.read()
        print(content)

except FileNotFoundError:
    print('File does not exist')

except PermissionError:
    print('Permission denied')
def open_file(path):
    if path.startswith('/tmp/'):
        print('Will create 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')

8.5.4. Exceptions logging

import logging

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

try:
    apollo13()
except RuntimeError as err:
    logging.error(err)

8.5.5. else

  • Executed when no exception occurred

def apollo11():
    print('Try landing on the Moon')

try:
    apollo11()
except Exception:
    print('Abort')
else:
    print('Landing a man on the Moon')

8.5.6. finally

  • Executed always (even if there was exception)

  • Used to close file, connection or transaction to database

def apollo11():
    print('Try landing on the Moon')

try:
    apollo11()
except Exception:
    print('Abort')
finally:
    print('Returning safely to the Earth')

8.5.7. else and finally

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')

8.5.8. 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

8.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

8.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

8.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')