11. Exceptions

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

11.2. Most common exceptions

11.2.1. AttributeError

  • Attribute reference or assignment fails

Listing 54. AttributeError exception
name = 'Jose'

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

11.2.2. ImportError, ModuleNotFoundError

  • Module could not be located

Listing 55. ModuleNotFoundError exception
import math
import match
# ModuleNotFoundError

11.2.3. IndexError

  • Sequence subscript is out of range

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

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

11.2.4. KeyError

  • Dictionary key is not found

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

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

11.2.5. NameError

  • Local or global name is not found

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

11.2.6. SyntaxError

  • Parser encounters a syntax error

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

# SyntaxError: invalid syntax

11.2.7. IndentationError

  • Syntax errors related to incorrect indentation

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

# IndentationError: unexpected indent

11.2.8. TypeError

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

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

11.2.9. ValueError

  • Argument is right type but an inappropriate value

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

11.3. Raising exceptions

11.3.1. Raise Exception without message

Listing 63. Raise Exception without message
raise RuntimeError

11.3.2. Exception with additional message

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

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

11.4. Traceback

11.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 "<input>", line 1, in <module>
#   File "/Applications/PyCharm 2019.2 EAP.app/Contents/helpers/pydev/_pydev_bundle/pydev_umd.py", line 197, in runfile
#     pydev_imports.execfile(filename, global_vars, local_vars)  # execute the script
#   File "/Applications/PyCharm 2019.2 EAP.app/Contents/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
#     exec(compile(contents+"\n", file, 'exec'), glob, loc)
#   File "/home/Developer/project/my_file.py", line 4, in <module>
#     apollo13()
#   File "/home/Developer/project/my_file.py", line 2, in apollo13
#     raise RuntimeError('Mid-flight Oxygen tank explosion')
# RuntimeError: Mid-flight Oxygen tank explosion

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

11.5. Catching exceptions

  • try

  • except

  • else

  • finally

11.5.1. Catch single exception

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


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

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

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

11.5.4. Exceptions logging

import logging

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

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

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

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

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

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

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

11.7. Defining own exceptions

11.7.1. Syntax

  • class which inherits from Exception

class MyError(Exception):
    pass


raise MyError
raise MyError('More verbose description')

11.7.2. Example

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

11.8. Real life use-case

from django.contrib.auth.models import User

try:
    user = User.objects.get(
        username=POST.get('username'),
        password=POST.get('password'),
    )
except User.DoesNotExists:
    print('Sorry, no such user in database')

11.9. Assignments

Todo

Create assignments