Errors and Exceptions

There are (at least) two distinguishable kinds of errors: syntax errors and exceptions.

8.1. Syntax Errors

  • a little ‘arrow’ pointing at the earliest point in the line where the error was detected. The error is caused by (or at least detected at) the token preceding the arrow

    1
    2
    3
    4
    5
    >>> True 4  False
    File "<stdin>", line 1
    True 4 False
    ^
    SyntaxError: invalid syntax
  • File name and line number are printed so you know where to look in case the input came from a script.

8.2. Exceptions

Errors detected during execution are called exceptions and are not unconditionally fatal

  • Exceptions come in different types, and the type is printed as part of the message
  • Standard exception names are built-in identifiers (not reserved keywords).
  • see more at Built-in Exceptions

8.3. Handling Exceptions

  • a user-generated interruption is signalled by raising the KeyboardInterrupt exception.

    1
    2
    3
    4
    5
    6
    while True:
    try:
    enter = input("Here is echo system:\n")
    print(enter)
    except KeyboardInterrupt:
    print("You can't get out @_@")
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    Here is echo system:
    sdfioaj
    sdfioaj
    Here is echo system:
    ^CYou can't get out @_@
    Here is echo system:


    Here is echo system:
    ^CYou can't get out @_@
    Here is echo system:
    Traceback (most recent call last):
    File "/Users/rubbish/test/ptest.py", line 3, in <module>
    enter = input("Here is echo system:\n")
    EOFError

    # enter a ^D as EOF to raise another Exception to get out.
    1
    2
    3
    4
    5
    6
    7
    8
    while True:
    try:
    enter = input("Here is echo system:\n")
    print(enter)
    except KeyboardInterrupt:
    print("You can't get out @_@")
    except:
    print('No way!!!')
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    Here is echo system:
    iowef
    iowef
    Here is echo system:
    No way!!!
    Here is echo system:
    ^CYou can't get out @_@
    Here is echo system:
    ffffffffffffffffffffffff
    ffffffffffffffffffffffff
    Here is echo system:
    ^CYou can't get out @_@
    Here is echo system:
    No way!!!
    zsh: terminated python3 ptest.py
    1
    2
    $ ps
    $ kill PythonPID
  • How try statement works ?

    1. the try claus(between try and except) are executed.

      —-> no exception occurs

      except claus are skipped and the execution of try is finished.

      —-> an exception occurs

    2. the rest of try claus is skipped

      —–> the exception type matches the exception named after the except keyword

      Execute the exception claus and the execution of try is finished.

      —–> the exception type doesn’t match any name.

    3. it is passed to the outer try statement

      —–> if there is an outer try statement

      The process repeated

      —–> if there does not exist an outer try statement

      The exception becomes an unhandled exception and the program break at the exception line.

    1
    2
    3
    4
    5
    6
    7
    8
    print("start")
    try:
    print('enter try claus')
    raise ValueError
    print('after ValueError') # this line will never be executed
    except ValueError:
    print('executing except claus')
    print("end")
    1
    2
    3
    4
    start
    enter try claus
    executing except claus
    end
  • An except clause may name multiple exceptions as a parenthesized tuple

    1
    2
    ... except (RuntimeError, TypeError, NameError):
    ... pass
  • Class derived from an Exception can be used as an Exception

  • A class in an except clause is compatible with an exception if it is the same class or a base class thereof

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class B(Exception):
    pass

    class C(B):
    pass

    class D(C):
    pass

    for cls in [B, C, D]:
    try:
    raise cls()
    except D:
    print("D")
    except C:
    print("C")
    except B:
    print("B")
    # This will print BCD
    # if the except clauses were reversed (with except B first), it would have printed B, B, B — the first matching except clause is triggered.
  • except can omit the exception name(s), to serve as a wildcard.

    • but this may mask an error, use it carefully
    • you can reraise the captured exception with raise
    1
    2
    3
    4
    5
    6
    import sys
    try:
    file = open('notexist.txt')
    except:
    print('Error',sys.exc_info())
    raise
    1
    2
    3
    4
    5
    Error (<class 'FileNotFoundError'>, FileNotFoundError(2, 'No such file or directory'), <traceback object at 0x10ee85040>)
    Traceback (most recent call last):
    File "/Users/******/test/ptest.py", line 4, in <module>
    file = open('notexist.txt')
    FileNotFoundError: [Errno 2] No such file or directory: 'notexist.txt'
  • The try...except claus have an optional else, must follow all except clauses, it is executed if try do not raise any exception. use else instead of nested try...except can avoid catch exceptions which are not raised by codes in try

    1
    2
    3
    4
    5
    6
    try:
    f = open('exist')
    except FileNotFoundError:
    print('file not found')
    else:
    print(f.read() , end='')
  • exceptions can have arguments, stored in instance.args, can be print directly use instance.__str__()

    1
    2
    3
    4
    5
    6
    7
    ins = Exception ('Hello' , 'world')
    argus = ins.args
    print(type(argus))
    x , y = argus
    print(x ,y )
    ss = ins.__str__()
    print(type(ss) , ss)
    1
    2
    3
    <class 'tuple'>
    Hello world
    <class 'str'> ('Hello', 'world')
  • If an unhandled exception has argus, they are printed as the last part (‘detail’) of the exception

    1
    2
    ins  = (Exception('I am an error'))
    raise ins
    1
    2
    3
    4
    Traceback (most recent call last):
    File "/Users/*****/test/ptest.py", line 2, in <module>
    raise ins
    Exception: I am an error
  • Exception handlers can handle exception in try claus, even it is in a function call (even indirectly)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    >>> def this_fails():
    ... x = 1/0
    ...
    >>> try:
    ... this_fails()
    ... except ZeroDivisionError as err:
    ... print('Handling run-time error:', err)
    ...
    Handling run-time error: division by zero

    8.4. Raising Exceptions

The raise statement allows the programmer to force a specified exception to occur.

  • raise must be followed with either an exception instance or an exception class (a class that derives from Exception). If an exception class is passed, it will be implicitly instantiated by calling its constructor with no arguments:

    1
    raise ValueError  # shorthand for 'raise ValueError()'

    8.5. Exception Chaining

The raise statement allows an optional from which enables chaining exceptions. This can be useful when transform exceptions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> def func():
... raise IOError
...
>>> try:
... func()
... except IOError as exc:
... raise RuntimeError('Failed to open database') from exc # exc must be an exception instance or None.
...
'''
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 2, in func
OSError

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
File "<stdin>", line 4, in <module>
RuntimeError: Failed to open database'''
  • Exception chaining happens automatically when an exception is raised inside an except or finally section. Exception chaining can be disabled by using from None idiom:

    1
    2
    3
    4
    5
    6
    7
    8
    try:
    open('database.sqlite')
    except IOError:
    raise RuntimeError from None
    '''
    Traceback (most recent call last):
    File "<stdin>", line 4, in <module>
    RuntimeError'''
  • see more at Built-in Exceptions

8.6. User-defined Exceptions

User Exceptions should typically be derived from the Exception class, either directly or indirectly.

  • User-defined Exceptions can do anything any other class can do, but are usually kept simple, often only offering a number of attributes that allow information about the error to be extracted by handlers for the exception.

  • When creating a module that can raise several distinct errors, a common practice is to create a base class for exceptions defined by that module, and subclass that to create specific exception classes for different error conditions:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Error(Exception):
    '''base exception class'''
    pass

    class InputError(Error):
    '''special input error'''
    def __init__(self ,message):
    self.message = message

    class OtherError(Error):
    '''other errors'''
    pass
  • Most exceptions are defined with names that end in “Error”, similar to the naming of the standard exceptions.

8.7. Defining Clean-up Actions

finally claus is intended to define clean-up actions that must be executed under all circumstances.

  • how finally works under different conditions

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    exec try
    if Exception occurs during try:
    if Exception handled by except:
    exec except claus
    if NewException occurs during except:
    exec finally claus
    raise NewException
    else:
    exec finally claus
    else:
    exec finally claus
    raise unhandled exception
    elif try reach a (break , continue , return) statement:
    exec finally claus
    exec (break or continue or return) statement
    else:
    exec finally claus
  • If a finally clause includes a return statement, the returned value will be the one from the finally clause’s return statement, not the value from the try clause’s return statement.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    >>> def divide(x, y):
    ... try:
    ... result = x / y
    ... except ZeroDivisionError:
    ... print("division by zero!")
    ... else:
    ... print("result is", result)
    ... finally:
    ... print("executing finally clause")
    ...
    >>> divide(2, 1)
    result is 2.0
    executing finally clause
    >>> divide(2, 0)
    division by zero!
    executing finally clause
    >>> divide("2", "1")
    executing finally clause
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "<stdin>", line 3, in divide
    TypeError: unsupported operand type(s) for /: 'str' and 'str'
  • In real world applications, the finally clause is useful for releasing external resources (such as files or network connections), regardless of whether the use of the resource was successful.

8.8. Predefined Clean-up Actions

Some objects define standard clean-up actions to be undertaken when the object is no longer needed, regardless of whether or not the operation using the object succeeded or failed.

Objects which, like files, provide predefined clean-up actions will indicate this in their documentation.