What is an exception?

An exception is an event that occurs during program execution and affects the normal execution of the program. Typically, an exception occurs when Python is unable to process a program properly. Exceptions are Python objects that represent an error. When an exception occurs in a Python script we need to catch it and handle it, otherwise the program will terminate execution.

When an uncaught exception occurs, Python will end the program and print a stack trace message, along with the exception name and additional information. The details are as follows.

1
2
3
4
5
>>> a = 1/0
Traceback (most recent call last):
  File "<pyshell#1>", line 1, in <module>
    a = 1/0
ZeroDivisionError: division by zero

Broadly speaking, errors are classified as errors and exceptions

  • Errors usually refer to syntax errors, which can be avoided artificially
  • Exceptions are problems that occur when the syntax is logically correct

The main purposes of catching and handling exceptions in Python are.

  • Error handling: In the event of a runtime error, the application may terminate unconditionally. Using exception handling, we can handle failure cases and avoid program termination.
  • Code separation: Error handling helps us to separate the code required for error handling from the main logic. The code associated with the error can be placed in an “except” block, which isolates it from the regular code containing the application logic.
  • Error differentiation: helps us isolate the different types of errors encountered during execution. We can have multiple “except” blocks, each dealing with a specific type of error.

Other applications.

  • Event notification: Exceptions can also be used as signals for certain conditions, without the need to pass result flags or test them explicitly in the program.
  • Special case handling: Sometimes there are situations that rarely occur and it is better to change the corresponding handling code to exception handling.
  • Special control flow: Exceptions are a high level “goto” that can be used as a basis for implementing special control flow. Such as reverse tracing, etc.

Python comes with a very powerful exception handling mechanism, providing many built-in exception classes that provide accurate feedback to the user. Python automatically puts all exception names in a built-in namespace, so programs don’t have to import the exceptions module to use exceptions.

Python’s built-in exception class inheritance hierarchy is as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
BaseException  # 所有异常的基类
 -- SystemExit  # 解释器请求退出
 -- KeyboardInterrupt  # 用户中断执行(通常是输入^C)
 -- GeneratorExit  # 生成器(generator)发生异常来通知退出
 -- Exception  # 常规异常的基类
      -- StopIteration  # 迭代器没有更多的值
      -- StopAsyncIteration  # 必须通过异步迭代器对象的__anext__()方法引发以停止迭代
      -- ArithmeticError  # 各种算术错误引发的内置异常的基类
      |    -- FloatingPointError  # 浮点计算错误
      |    -- OverflowError  # 数值运算结果太大无法表示
      |    -- ZeroDivisionError  # 除(或取模)零 (所有数据类型)
      -- AssertionError  # 当assert语句失败时引发
      -- AttributeError  # 属性引用或赋值失败
      -- BufferError  # 无法执行与缓冲区相关的操作时引发
      -- EOFError  # 当input()函数在没有读取任何数据的情况下达到文件结束条件(EOF)时引发
      -- ImportError  # 导入模块/对象失败
      |    -- ModuleNotFoundError  # 无法找到模块或在在sys.modules中找到None
      -- LookupError  # 映射或序列上使用的键或索引无效时引发的异常的基类
      |    -- IndexError  # 序列中没有此索引(index)
      |    -- KeyError  # 映射中没有这个键
      -- MemoryError  # 内存溢出错误(对于Python 解释器不是致命的)
      -- NameError  # 未声明/初始化对象 (没有属性)
      |    -- UnboundLocalError  # 访问未初始化的本地变量
      -- OSError  # 操作系统错误,EnvironmentError,IOError,WindowsError,socket.error,select.error和mmap.error已合并到OSError中,构造函数可能返回子类
      |    -- BlockingIOError  # 操作将阻塞对象(e.g. socket)设置为非阻塞操作
      |    -- ChildProcessError  # 在子进程上的操作失败
      |    -- ConnectionError  # 与连接相关的异常的基类
      |    |    -- BrokenPipeError  # 另一端关闭时尝试写入管道或试图在已关闭写入的套接字上写入
      |    |    -- ConnectionAbortedError  # 连接尝试被对等方中止
      |    |    -- ConnectionRefusedError  # 连接尝试被对等方拒绝
      |    |    -- ConnectionResetError    # 连接由对等方重置
      |    -- FileExistsError  # 创建已存在的文件或目录
      |    -- FileNotFoundError  # 请求不存在的文件或目录
      |    -- InterruptedError  # 系统调用被输入信号中断
      |    -- IsADirectoryError  # 在目录上请求文件操作(例如 os.remove())
      |    -- NotADirectoryError  # 在不是目录的事物上请求目录操作(例如 os.listdir())
      |    -- PermissionError  # 尝试在没有足够访问权限的情况下运行操作
      |    -- ProcessLookupError  # 给定进程不存在
      |    -- TimeoutError  # 系统函数在系统级别超时
      -- ReferenceError  # weakref.proxy()函数创建的弱引用试图访问已经垃圾回收了的对象
      -- RuntimeError  # 在检测到不属于任何其他类别的错误时触发
      |    -- NotImplementedError  # 在用户定义的基类中,抽象方法要求派生类重写该方法或者正在开发的类指示仍然需要添加实际实现
      |    -- RecursionError  # 解释器检测到超出最大递归深度
      -- SyntaxError  # Python 语法错误
      |    -- IndentationError  # 缩进错误
      |         -- TabError  # Tab和空格混用
      -- SystemError  # 解释器发现内部错误
      -- TypeError  # 操作或函数应用于不适当类型的对象
      -- ValueError  # 操作或函数接收到具有正确类型但值不合适的参数
      |    -- UnicodeError  # 发生与Unicode相关的编码或解码错误
      |         -- UnicodeDecodeError  # Unicode解码错误
      |         -- UnicodeEncodeError  # Unicode编码错误
      |         -- UnicodeTranslateError  # Unicode转码错误
      -- Warning  # 警告的基类
           -- DeprecationWarning  # 有关已弃用功能的警告的基类
           -- PendingDeprecationWarning  # 有关不推荐使用功能的警告的基类
           -- RuntimeWarning  # 有关可疑的运行时行为的警告的基类
           -- SyntaxWarning  # 关于可疑语法警告的基类
           -- UserWarning  # 用户代码生成警告的基类
           -- FutureWarning  # 有关已弃用功能的警告的基类
           -- ImportWarning  # 关于模块导入时可能出错的警告的基类
           -- UnicodeWarning  # 与Unicode相关的警告的基类
           -- BytesWarning  # 与bytes和bytearray相关的警告的基类
           -- ResourceWarning  # 与资源使用相关的警告的基类。被默认警告过滤器忽略。

Exception catching and handling

We can’t guarantee that the programs we write will always run correctly, and the purpose of exception catching and handling is to ensure that the worst-case problems the program gets are managed properly.

In Python, exceptions are caught and handled with try except statement blocks, the basic syntactic structure of which is shown below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
try:
    可能产生异常的代码块
except [ (Error1, Error2, ... ) [as e] ]:
    处理异常的代码块1
except [ (Error3, Error4, ... ) [as e] ]:
    处理异常的代码块2
except  [Exception]:
    处理其它异常
else:
    没有异常时执行的代码块
finally:
    不管有没有异常都会执行的代码块

The way try works is that when you start a try statement, python marks it in the context of the current program so that you can return to it when an exception occurs, the try clause is executed first, and what happens next depends on whether an exception occurs during execution.

If an exception occurs while the statement after try is executing, Python jumps back to try and executes the first exception clause that matches the exception, the exception is handled, and control flows through the entire try statement (unless a new exception is raised while the exception is being handled). If an exception occurs in the statement after try and there is no matching except clause, the exception is passed to the upper level of try, or to the top level of the program (which will end the program and print the default error message). If no exception occurs during the execution of the try clause, Python will execute the statement after the else statement (if there is an else), and then control flows through the entire try statement.

Code example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
try:
    file_reader = open("a.json")
    print(file_reader.read())
except FileNotFoundError:
    print("The File Cannot be Found")
except:
    print("This is the Generic Error")
finally:
    print("Closing File Reader...")
    file_reader.close()

The Exception class is the parent of all Python exception classes, so except Exception will be able to catch any exception; in other words, it is the catch-all exception handling syntax. There is one kind of error that there is no way to catch: indentation errors.

raise: manually raise an exception

Sometimes an exception can be used as a flag for code to run, and by proactively triggering an exception you can change the course of the code, thus improving code robustness.

The active trigger exception requires the raise keyword, which has the following syntax structure.

1
raise [Exception [, args [, traceback]]]

Example.

1
2
3
4
5
6
try:
    a = int(input("Enter a positive integer value: "))
    if a <= 0:
        raise ValueError("This is not a positive number!!")
except ValueError as ve:
    print(ve)

The statement Exception is the type of the exception (e.g., ValueError) The parameter is an exception parameter value. This parameter is optional, and if not provided, the exception parameter is “None”. The last parameter is optional (rarely used in practice) and, if present, is a trace exception object.

Custom Exceptions

You can have your own exceptions by creating a new exception class. Exception classes inherit from the Exception class, either directly, or indirectly, e.g.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class MyError(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)


try:
    raise MyError(2 * 2)
    # raise MyError('oops!')
except MyError as e:
    print('My exception occurred, value:', e.value)

Assertion: assert condition

Python assert is used to determine an expression and trigger an exception if the condition of the expression is false. Assertions can return an error directly if the condition is not met, without having to wait for the program to run and then crash. For example, if our code can only run on Linux, we can first determine if the current system meets the condition.

The syntax format is as follows.

1
assert expression

Equivalent to.

1
2
if not expression:
    raise AssertionError(arguments)

Example.

1
2
import sys
assert ('linux' in sys.platform), "该代码只能在 Linux 下执行"

assert looks good, however it is not pleasant to use. For example, someone tells you that the program is wrong, but doesn’t tell you what’s wrong. Many times such assert is better than not writing it at all. A common solution is to use a testing framework or other packages instead: py.test, unittest, ptest, assertpy…

traceback Get detailed exception information

The output of try…except… only lets you know that the error was reported, but not in which file, function, or line. Using the traceback module, you can get a very clear picture of where the error is.

1
2
3
4
5
6
import traceback

try:
    1 / 0
except Exception as e:
    traceback.print_exc()

Output.

1
2
3
4
Traceback (most recent call last):
File "test_traceback.py", line 3, in <module>
1/0
ZeroDivisionError: integer division or modulo by zero

Python programs get their traceback information from an object called the traceback object, which is usually obtained through the function sys.exc_info().

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import sys


def func1():
    raise Exception("--func1 exception--")


def test1():
    try:
        func1()
    except Exception as e:
        print(e)


def test2():
    try:
        func1()
    except Exception as e:
        exc_type, exc_value, exc_traceback_obj = sys.exc_info()
        print("exc_type: %s" % exc_type)
        print("exc_value: %s" % exc_value)
        print("exc_traceback_obj: %s" % exc_traceback_obj)


if __name__ == '__main__':
    test1()
    test2()

Output results.

1
2
3
4
5
6
--func1 exception--
exc_type: <class 'Exception'>
exc_value: --func1 exception--
exc_traceback_obj: <traceback object at 0x0000024D2F6A22C8>

Process finished with exit code 0

As we can see from the above example, sys.exc_info() gets the information about the currently handled exception and returns a tuple, the first data of the tuple is the type of the exception, the second return value is the value of the exception, and the third is the traceback object.

With the traceback object we can use the traceback module to print and format the information about the traceback.