5.5. Exceptions

5.5.1. Rationale

  • Used when error occurs

  • You can catch exception and handles erroneous situation

  • Exception example situations:

    • File does not exists

    • No permissions to read file

    • Function argument is invalid type (ie. int('one'))

    • Value is incorrect (ie. negative Kelvin temperature)

    • Network or database connection could not be established

5.5.2. Most Common Exceptions

Listing 5.40. AttributeError - Attribute reference or assignment fails
name = 'Jan'
name.append('Twardowski')
# Traceback (most recent call last):
#     ...
# AttributeError: 'str' object has no attribute 'append'
Listing 5.41. ImportError, ModuleNotFoundError - Module could not be located
import math
import match
# Traceback (most recent call last):
#     ...
# ModuleNotFoundError: No module named 'match'
Listing 5.42. IndexError - Sequence subscript is out of range
DATA = ['a', 'b', 'c']
DATA[100]
# Traceback (most recent call last):
#     ...
# IndexError: list index out of range
Listing 5.43. KeyError - Dictionary key is not found
DATA = {'a': 1, 'b': 2}
DATA['x']
# Traceback (most recent call last):
#     ...
# KeyError: 'x'
Listing 5.44. NameError - Local or global name is not found
print(firstname)
# Traceback (most recent call last):
#     ...
# NameError: name 'firstname' is not defined
Listing 5.45. SyntaxError - Parser encounters a syntax error
if True
    print('Yes')
# Traceback (most recent call last):
#   File "<stdin>", line 1
#     if True
#           ^
# SyntaxError: invalid syntax
Listing 5.46. IndentationError - Syntax errors related to incorrect indentation
if True:
   print('Hello!')
    print('My name...')
   print('José Jiménez')
# Traceback (most recent call last):
#   File "<stdin>", line 1
#     print('My name...')
#     ^
# IndentationError: unexpected indent
Listing 5.47. TypeError - Operation or function is applied to an object of inappropriate type
42 + 'a'
# Traceback (most recent call last):
#     ...
# TypeError: unsupported operand type(s) for +: 'int' and 'str'

'a' + 42
# Traceback (most recent call last):
#     ...
# TypeError: can only concatenate str (not "int") to str

a[1.5]
# Traceback (most recent call last):
#     ...
# TypeError: list indices must be integers or slices, not float

a, b = 1
# Traceback (most recent call last):
#     ...
# TypeError: cannot unpack non-iterable int object
Listing 5.48. ValueError Argument has an invalid value
a, b, c = 1, 2
# Traceback (most recent call last):
#     ...
# ValueError: not enough values to unpack (expected 3, got 2)

a, b = 1, 2, 3
# Traceback (most recent call last):
#     ...
# ValueError: too many values to unpack (expected 2)

float('one')
# Traceback (most recent call last):
#     ...
# ValueError: could not convert string to float: 'one'

int('one')
# Traceback (most recent call last):
#     ...
# ValueError: invalid literal for int() with base 10: 'one'

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

5.5.4. Raising Exceptions

Listing 5.49. Raise Exception without message
raise RuntimeError
# Traceback (most recent call last):
#     ...
# RuntimeError
Listing 5.50. Exception with additional message
raise RuntimeError('Some message')
# Traceback (most recent call last):
#     ...
# RuntimeError: Some message

5.5.5. Use Case

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

if float(temperature) < 0:
    raise ValueError('Kelvin temperature cannot be negative')
# Traceback (most recent call last):
#     ...
# ValueError: Kelvin temperature cannot be negative
def convert(temperature):

    if type(temperature) not in {float, int}:
        raise TypeError('Temperature must be int or float')

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

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


apollo13()
# Traceback (most recent call last):
#     ...
# RuntimeError: Oxygen tank explosion
def apollo18():
    raise NotImplementedError('Mission dropped due to budget cuts')


apollo18()
# Traceback (most recent call last):
#     ...
# NotImplementedError: Mission dropped due to budget cuts

5.5.6. Assertion

  • Raises AssertionError if argument is False

  • Can have optional message

import sys

assert sys.version_info >= (3, 8)
# Traceback (most recent call last):
#     ...
# AssertionError

assert sys.version_info >= (3, 8), "Python 3.8+ required."
# Traceback (most recent call last):
#     ...
# AssertionError: Python 3.8+ required.

5.5.7. 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 "/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
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

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

5.5.9. Catching Exceptions

  • try

  • except

  • else

  • finally

Listing 5.51. 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 5.52. 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 5.53. Catch many exceptions with different handling
try:
    with open(r'/tmp/my-file.txt') as file:
        print(file.read())
except FileNotFoundError:
    print('File does not exist')
except PermissionError:
    print('Permission denied')

# File does not exist
Listing 5.54. 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

5.5.10. 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 5.55. 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 5.56. 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("You'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
# You're GO for landing
# Returning safely to the Earth

5.5.11. Pokemon Exception Handling

  • "Gotta catch 'em all"

  • Ctrl-C raises KeyboardInterrupt

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

5.5.12. Defining Own Exceptions

  • class which inherits from Exception

class MyError(Exception):
    pass


raise MyError
# Traceback (most recent call last):
#     ...
# MyError

raise MyError('More verbose description')
# Traceback (most recent call last):
#     ...
# MyError: More verbose description
Listing 5.59. Django Framework Use-case of Custom Exceptions
from django.contrib.auth.models import User


def login(request):
    username = request.POST.get('username')
    password = request.POST.get('password')

    try:
        user = User.objects.get(username, password)
    except User.DoesNotExist:
        print('Sorry, no such user in database')
Listing 5.60. Django Framework Use-case of Custom Exceptions
class Dragon:
    def take_damage(self, damage):
        raise self.IsDead

    class IsDead(Exception):
        pass


wawelski = Dragon()

try:
    wawelski.take_damage(10)
except Dragon.IsDead:
    print('Dragon is dead')

5.5.13. Exit Status Code

  • exit status 0 - no error

  • any other exit status - error

  • This will not work in Jupyter

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

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

5.5.14. Assignments

5.5.14.1. Exception Example

  • Assignment name: Exception Example

  • Last update: 2020-10-01

  • Complexity level: easy

  • Lines of code to write: 0 lines

  • Estimated time of completion: 3 min

  • Solution: solution/exception_example.py

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

5.5.14.2. Exception Raise

  • Assignment name: Exception Raise

  • Last update: 2020-10-01

  • Complexity level: easy

  • Lines of code to write: 5 lines

  • Estimated time of completion: 5 min

  • Solution: solution/exception_raise.py

English
  1. Ask user to input age

  2. If user has less than 18 years

  3. Raise an exception PermissionError with message "Adults only"

Polish
  1. Poproś użytkownika o wprowadzenie wieku

  2. Jeżeli użytkownik ma mniej niż 18 lat

  3. Wyrzuć wyjątek PermissionError z komunikatem "Adults only"

5.5.14.3. Exception Catch

  • Assignment name: Exception Catch

  • Last update: 2020-10-01

  • Complexity level: easy

  • Lines of code to write: 6 lines

  • Estimated time of completion: 5 min

  • Solution: solution/exception_catch.py

English
  1. Ask user to input temperature in Kelvins

  2. Convert temperature to float

  3. If cannot type cast to float, then print 'Invalid temperature' and exit with status code 1

  4. Print temperature

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

  2. Przekonwertuj temperaturę do float

  3. Jeżeli nie można rzutować do float, to wypisz "Invalid temperature" i wyjdź z kodem błędu 1

  4. Wypisz temperaturę

5.5.14.4. Exception Define

  • Assignment name: Exception Define

  • Last update: 2020-10-01

  • Complexity level: easy

  • Lines of code to write: 6 lines

  • Estimated time of completion: 5 min

  • Solution: solution/exception_define.py

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