8.1. Exceptions

8.1.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.1.2. Most common exceptions

8.1.2.1. AttributeError

  • Attribute reference or assignment fails

Listing 135. AttributeError exception
name = 'Jan'
name.append('Twardowski')
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# AttributeError: 'str' object has no attribute 'append'

8.1.2.2. ImportError, ModuleNotFoundError

  • Module could not be located

Listing 136. ModuleNotFoundError exception
import math
import match
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# ModuleNotFoundError: No module named 'match'

8.1.2.3. IndexError

  • Sequence subscript is out of range

Listing 137. IndexError exception
DATA = ['a', 'b', 'c']
DATA[100]
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# IndexError: list index out of range

8.1.2.4. KeyError

  • Dictionary key is not found

Listing 138. KeyError exception
DATA = {'a': 1, 'b': 2}
DATA['x']
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# KeyError: 'x'

8.1.2.5. NameError

  • Local or global name is not found

Listing 139. KeyError exception
print(first_name)
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# NameError: name 'first_name' is not defined

8.1.2.6. SyntaxError

  • Parser encounters a syntax error

Listing 140. SyntaxError exception
if True
    print('Yes')
# Traceback (most recent call last):
#   File "<stdin>", line 1
#     if True
#           ^
# SyntaxError: invalid syntax

8.1.2.7. IndentationError

  • Syntax errors related to incorrect indentation

Listing 141. IndentationError exception
if True:
   print('Hello!')
    print('My name...')
   print('Jose Jimenez')
# Traceback (most recent call last):
#   File "<stdin>", line 1
#     print('My name...')
#     ^
# IndentationError: unexpected indent

8.1.2.8. TypeError

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

Listing 142. TypeError exception
42 + 1
# 43

'a' + 'b'
# 'ab'

42 + 'a'
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# TypeError: unsupported operand type(s) for +: 'int' and 'str'

'a' + 42
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# TypeError: can only concatenate str (not "int") to str

8.1.2.9. ValueError

  • Argument is right type but an inappropriate value

Listing 143. ValueError exception
float(1.2)
# 1.2

float(1,2)
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# TypeError: float expected at most 1 arguments, got 2

8.1.3. Raising exceptions

Listing 144. Raise Exception without message
raise RuntimeError
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# RuntimeError
Listing 145. Exception with additional message
raise RuntimeError('Some message')
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# RuntimeError: Some message

8.1.4. Use case

temperature = input('Type temperature [Kelvin]: ')
# Type temperature [Kelvin]: -10<ENTER>

if float(temperature) < 0:
    raise ValueError
# Traceback (most recent call last):
#   File "<stdin>", line 2, in <module>
# ValueError
temperature = input('Type Temperature [Kelvin]: ')

if type(temperature) not in (float, int):
    raise TypeError('Argument ``a`` must be int or float')

if float(temperature) < 0:
    raise ValueError('Kelvin temperature cannot be negative')

print(temperature)
def apollo13():
    raise RuntimeError('Oxygen tank explosion')

apollo13()
# Traceback (most recent call last):
#   File "<stdin>", line 5, in <module>
#   File "<stdin>", line 2, in apollo13
# RuntimeError: Oxygen tank explosion
def apollo18():
    raise NotImplementedError('Mission dropped due to budget cuts')

apollo18()
# Traceback (most recent call last):
#   File "<stdin>", line 5, in <module>
#   File "<stdin>", line 2, in apollo18
# NotImplementedError: Mission dropped due to budget cuts

8.1.5. Assertion

  • Raises AssertionError if argument is False

  • Can have optional message

import sys

assert sys.version_info >= (3, 8)
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# AssertionError

assert sys.version_info >= (3, 8), "Python 3.8+ required."
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# AssertionError: Python 3.8+ required.

8.1.6. Traceback

8.1.6.1. Traceback analysis

  • Stacktrace is 8 levels deep, it's not Java's 200 ;)

raise RuntimeError
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# RuntimeError
raise RuntimeError('Huston we have a problem')
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# RuntimeError: Huston we have a problem
def apollo13():
    raise RuntimeError('Oxygen tank explosion')

apollo13()
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
#   File "<stdin>", line 2, in apollo13
# RuntimeError: Oxygen tank explosion
def apollo13():
    raise RuntimeError('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/python/my_script.py", line 4, in <module>
#     apollo13()
#   File "/home/python/my_script.py", line 2, in apollo13
#     raise RuntimeError('Oxygen tank explosion')
# RuntimeError: Oxygen tank explosion

8.1.6.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 = 2


def apollo13():
    raise RuntimeError('Oxygen tank explosion')

apollo13()
# Traceback (most recent call last):
#   File "/home/python/my_script.py", line 4, in <module>
#     apollo13()
#   File "/home/python/my_script.py", line 2, in apollo13
#     raise RuntimeError('Oxygen tank explosion')
# RuntimeError: Oxygen tank explosion

8.1.7. Catching exceptions

  • try

  • except

  • else

  • finally

Listing 146. Catch single exception
def apollo13():
    raise RuntimeError('Oxygen tank explosion')


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

# Houston we have a problem!
Listing 147. Catch many exceptions with the same handling
def apollo13():
    raise RuntimeError('Oxygen tank explosion')


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

# Houston we have a problem!
Listing 148. Catch many exceptions with different handling
try:
    with open(r'/tmp/iris.csv') as file:
        print(file.read())

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

except PermissionError:
    print('Permission denied')

# File does not exist
Listing 149. Exceptions logging
import logging


def apollo13():
    raise RuntimeError('Oxygen tank explosion')

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

# ERROR:root:Oxygen tank explosion

8.1.8. else and finally

  • else is executed when no exception occurred

  • finally is executed always (even if there was exception)

  • Used to close file, connection or transaction to database

Listing 150. else is 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')

# Try landing on the Moon
# Landing a man on the Moon
Listing 151. finally is executed always (even if there was exception)
def apollo11():
    print('Try landing on the Moon')

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

# Try landing on the Moon
# Returning safely to the Earth
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')

# Program P63 - Landing Manoeuvre Approach Phase
# Yo're GO for landing
# Returning safely to the Earth

8.1.9. Always catch exceptions!

  • Ctrl-C raises KeyboardInterrupt

Listing 152. User cannot simply kill program with Ctrl-C
while True:
    try:
        number = float(input('Type number: '))
    except:
        continue
Listing 153. User can kill program with Ctrl-C
while True:
    try:
        number = float(input('Type number: '))
    except Exception:
        continue

8.1.10. 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.1.11. Defining own exceptions

  • class which inherits from Exception

class MyError(Exception):
    pass


raise MyError
# Traceback (most recent call last):
#   File "<stdin>", line 5, in <module>
# MyError

raise MyError('More verbose description')
# Traceback (most recent call last):
#   File "<stdin>", line 5, in <module>
# MyError: More verbose description

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

8.1.12. Exit Status Code

  • exit with status 0 - no error

  • any other status - error

try:
    float('hello')
except ValueError:
    print('Cannot type cast to float')
    exit(1)

# Cannot type cast to float
# [...] program exited with status 1

8.1.13. Assignments

8.1.13.1. Example

English
  1. Ask user to input angle in degrees

  2. Cotangens for 180 degrees is infinite

  3. Define own exception

  4. If user typed angle equal to 180, raise your exception

Polish
  1. Poproś użytkownika o wprowadzenie kąta

  2. Cotangens dla konta 180 ma nieskończoną wartość

  3. Zdefiniuj własny wyjątek

  4. Jeżeli użytkownik wprowadził kąt równy 180, podnieś swój wyjątek

Solution
class CotangentError(Exception):
    pass


degrees = input('Type angle [deg]: ')

if int(degrees) == 180:
    raise CotangentError('Cotangent for 180 degrees is infinite')

8.1.13.2. Raise Exception

English
  1. Ask user to input age

  2. If user has more than 18 years

  3. Raise an exception PermissionError with message "Only for kids"

Polish
  1. Poproś użytkownika o wprowadzenie wieku

  2. Jeżeli użytkownik ma więcej niż 18 lat

  3. Wyrzuć wyjątek PermissionError z komunikatem "Only for kids"

8.1.13.3. Catch Exception

English
  1. Ask user to input temperature in Kelvins

  2. Convert temperature to float

  3. Print 'Invalid temperature' if cannot type cast to float

  4. Print temperature

Polish
  1. Poproś użytkownika o wprowadzenie temperatury w Kelwinach

  2. Przekonwertuj temperaturę do float

  3. Wypisz "Invalid temperature" jak nie można rzutować do float

  4. Wypisz temperaturę

8.1.13.4. Define Exception

English
  1. Ask user to input temperature in Kelvins

  2. User will always type proper int or float

  3. Define exception for negative temperature

  4. Raise your exception if temperature is less than 0

Polish
  1. Poproś użytkownika o wprowadzenie temperatury w Kelwinach

  2. Użytkownik zawsze poda poprawne int lub float

  3. Zdefiniuj wyjątek dla temperatur ujemnych

  4. Podnieś własny wyjątek jeżeli temperatura jest poniżej 0