Using exceptions

https://docs.python.org/3/library/exceptions.html

https://docs.python.org/3/reference/executionmodel.html#exceptions

https://nedbatchelder.com/text/exceptions-vs-status.html
https://nedbatchelder.com/text/exceptions-in-the-rainforest.html

INTRODUCTION

Exceptions are a means of breaking out of the normal flow of control of a code block in order to handle errors or other exceptional conditions. An exception is 'raised' at the point where the error is detected; it may be 'handled' by the surrounding code block or by any code block that directly or indirectly invoked the code block where the error occurred.

When an exception is not handled at all, the interpreter terminates execution of the program, or returns to its interactive main loop. In either case, it prints a stack traceback, except when the exception is 'SystemExit'.

Broadly speaking, there are two ways to handle errors as they pass from layer to layer in software: throwing exceptions and returning status codes.

Why are we using exceptions [instead of error codes]?
(a) To raport errors or warnings [both methods].
(b) Exceptions are propagated by default up the call stack (error codes do not). The intermediate layers of code may have no knowledge of them (clean code).
(c) An exception class hierarchy can be used for related results.
(d) Exceptions cannot be ignored (unless we want this), return codes can.
(e) Exceptions can carry richer information than error codes.
(f) Exceptions leave the primary channel (function returns) available for the primary work. Status returns can’t even be used with some functions [C++].

+----------------------------------------+---------------------+
| Statements                             | Meaning             |
+----------------------------------------+---------------------+
| raise MyException("message")           | raising exceptions  |
| try/except/finally                     | handling exceptions |
| class MyException(Exception): pass     | creating exceptions |
| assert expression                      | assertions          |
| with ContextManager() as context: pass | context managers    |
+----------------------------------------+---------------------+

The built-in exceptions can be generated by the interpreter or built-in functions.


print(23 / 0)   # ZeroDivisionError
L = []     # an empty list
print(L[5])   # IndexError
D = {}    # an empty dictionary
print(D["key"])   # KeyError

EXCEPTION HIERARCHY


>>> import exceptions   # Py2.6+
>>> help(exceptions)

>>> import builtins   # Py3, built-in functions, exceptions, and other objects
>>> help(builtins)

BaseException
    +-- SystemExit
    +-- KeyboardInterrupt
    +-- GeneratorExit
    +-- Exception   # all other current built-in exceptions
        +-- StopIteration
        +-- StandardError
        |    +-- ArithmeticError
        |    +-- AssertionError
        |    +-- AttributeError
        |    +-- NameError
        |    +-- SyntaxError
        |        +-- IndentationError
        |    +-- TypeError
        |    +-- ValueError
        |    +-- ImportError
        |    +-- MemoryError
        |    +-- RuntimeError
        |        +-- NotImplementedError
        |    +-- EnvironmentError
        |        +-- IOError
        |        +-- OSError
        |    +-- LookupError
        |        +-- IndexError
        |        +-- KeyError
        |    +-- ...
        +-- Warning
            +-- DeprecationWarning
            +-- UserWarning
            +-- SyntaxWarning
            +-- RuntimeWarning
            +-- ...