Detailed explanation of Python error handling mechanism

In program development, no matter how careful you are in writing code, runtime errors are inevitable - such as zero encountered during division, failure to convert a string to a number, or opening a non-existent file.

In the early days, many languages ​​relied on the "return error code" method to handle exceptions. However, the shortcomings of this solution are obvious: normal results and errors are mixed together and it is difficult to distinguish. Every time a function is called, a judgment must be made. When reporting with multiple layers of nesting, the code will become redundant and difficult to read.

Fortunately, Python provides a set oftry...except...else...finally...The structured exception-handling mechanism completely separates error handling logic and business logic, making the code elegant and robust.


1. Core usage of try-except

1.1 Basic process demonstration

Let’s look at the most classic example first:

try:
    # 这里放【可能抛出异常】的核心业务代码
    print('try... 开始执行可能出错的代码')
    result = 10 / 0       # 会触发 ZeroDivisionError
    print('result:', result)  # 抛出异常后,这行不会执行
except ZeroDivisionError as e:
    # 捕获 ZeroDivisionError,执行【对应处理逻辑】
    print('except:', e)
finally:
    # 无论是否出错、甚至 except/else 里有 return/break,【这部分都会执行】
    print('finally... 收尾工作(比如关闭文件、释放连接)')
print('END')  # 错误处理完成后,程序继续向下走

The running process can be summarized in one sentence:

Rush firsttryblock, if an error occurs, the corresponding one will be jumped.except, leave no matter whatfinally, and finally the program ends normally.


2. Advanced: multiple exceptions and else clauses

2.1 Catching various exceptions

A piece of code may throw multiple errors, we can write multipleexceptProcessed separately:

try:
    print('try...')
    result = 10 / int('a')  # 这里是 ValueError
    print('result:', result)
except ValueError as e:
    print('捕获到值错误:', e)
except ZeroDivisionError as e:
    print('捕获到除零错误:', e)
finally:
    print('finally...')
print('END')

2.2 else when there is no error

iftryThe block ** does not throw an exception at all ** and will be executed.elseClause - The advantage of this design is that it clearly separates "business logic" and "supplementary logic after error-free":

try:
    print('try...')
    result = 10 / int('2')
except ValueError as e:
    print('捕获到值错误:', e)
except ZeroDivisionError as e:
    print('捕获到除零错误:', e)
else:
    print('✅ 无错误!result =', result)  # 只有 try 顺利跑完才会来这里
finally:
    print('finally...')
print('END')

3. Inheritance relationship of Python exceptions

All exceptions in Python are objects and inherit from the top-level base classBaseException. In our usual development, 99% of the time we only need to capture its subclassesExceptionor more specific exception type.

Several common exception levels:

  • BaseException(Top level, generally not captured directly)
    • Exception(General error base class, commonly used in development)
      • ArithmeticError(arithmetic error base class)
        • ZeroDivisionError(divide by zero/modulo zero)
      • LookupError(index/key error base class)
        • IndexError(list out of bounds)
        • KeyError(key that does not exist in dictionary)
      • TypeError(Type mismatch, such as givinglen()Passed an integer)
      • ValueError(Invalid value, e.g.int('abc')
      • OSError(Base class for operating system errors, such as opening a file that does not exist)

The complete hierarchy can be found in Python 官方异常文档.


4. Debugging tool: call stack Traceback

When you do not catch an exception, Python will automatically print out a detailed call chain (Traceback) to help you quickly locate the error's "trigger point" and "propagation path."

4.1 Call stack example

def foo(s):
    return 10 / int(s)  # 这里是错误的【触发点】

def bar(s):
    return foo(s) * 2  # 这里是错误的【第一层传播】

def main():
    bar('0')  # 这里是错误的【第二层传播】

if __name__ == '__main__':
    main()

Output after running:

Traceback (most recent call last):
  File "err_traceback.py", line 12, in <module>
    main()
  File "err_traceback.py", line 9, in main
    bar('0')
  File "err_traceback.py", line 6, in bar
    return foo(s) * 2
  File "err_traceback.py", line 3, in foo
    return 10 / int(s)
ZeroDivisionError: division by zero

4.2 How to quickly read the call stack

Remember to read from bottom to top:

  1. The last line is error type and core reason (this is the key to solving the problem)
  2. Going up are the calls at each layer of the propagation chain: the closest one is the trigger point, and the farthest one is the program entry.

5. Record errors without interrupting the program: logging module

If you don’t want the program to crash because of an error, and you need to completely record the error information (such as Traceback), you can use Python’s built-inloggingModule:

import logging

# 可以先配置一下 logging 的级别(可选)
logging.basicConfig(level=logging.ERROR)

def foo(s):
    return 10 / int(s)

def bar(s):
    return foo(s) * 2

def main():
    try:
        bar('0')
    except Exception as e:
        # logging.exception 会自动记录 Traceback
        logging.exception('程序运行出错:')
    print('✅ 虽然出错了,但程序继续执行')

if __name__ == '__main__':
    main()

6. Custom exceptions and exception delivery

6.1 Custom exceptions

When the built-in exception types are not enough, you can inheritException(or its appropriate subclass) define your own exception:

# 继承 ValueError 比较合理,因为是“值无效”类的错误
class InvalidAgeError(ValueError):
    pass

def check_age(age):
    if not isinstance(age, int):
        raise TypeError('年龄必须是整数')
    if age < 0 or age > 120:
        raise InvalidAgeError(f'无效年龄:{age},必须在 0-120 之间')
    print(f'年龄 {age} 验证通过')

try:
    check_age(150)
except InvalidAgeError as e:
    print('捕获到自定义异常:', e)

Best Practices for custom exceptions:

  1. Prioritize the use of built-in exceptions (don’t reinvent the wheel)
  2. Only customize when "there is special processing logic" or "clearer semantics are needed"
  3. The inheritance relationship must be reasonable (such as "invalid age" inheritanceValueErrorinstead ofOSError

6.2 Exception delivery (rethrow)

Sometimes we catch an exception not to handle it, but to do some recording or conversion, and then throw it to the upper caller for processing:

def load_config():
    try:
        f = open('config.txt', 'r')
        # ... 读取配置 ...
        f.close()
    except FileNotFoundError as e:
        print('⚠️ 配置文件不存在,先记录日志再扔给上层')
        logging.exception(e)
        raise  # 直接 re-raise 原异常

Another scenario is to convert the exception type:

class ConfigLoadError(Exception):
    pass

def load_config():
    try:
        f = open('config.txt', 'r')
        # ...
    except (FileNotFoundError, PermissionError) as e:
        # 把底层的文件错误,转换成更上层的“配置加载错误”
        raise ConfigLoadError('配置加载失败') from e

7. 5 best practices for error handling

  1. Capture accurately, don’t grab randomly Only catch exceptions that you know how to handle, such as just catchingFileNotFoundErrorThat's enough. Don't grab them as soon as you come up.ExceptionevenBaseException

  2. Avoid empty except except:All exceptions will be caught (including keyboard interruptsCtrl+C), it is very dangerous and should never be written.

  3. try block should be small Only put in "the line/lines that may throw an exception", don't wrap the entire function.

  4. Throw exceptions with context For example, don’t just writeraise ValueError, to writeraise ValueError(f'无效参数:{s},应该是数字')

  5. Documentation of possible exceptions Write clearly in the docstring of the function what exceptions will be thrown and why.


8. Small exercise: Repair a summation code

Practice code

from functools import reduce

def str2num(s):
    return int(s)  # 这里是问题所在!不能处理浮点数

def calc(exp):
    ss = exp.split('+')
    ns = map(str2num, ss)
    return reduce(lambda acc, x: acc + x, ns)

def main():
    r = calc('100 + 200 + 345')
    print('100 + 200 + 345 =', r)
    r = calc('99 + 88 + 7.6')  # 这里会报 ValueError
    print('99 + 88 + 7.6 =', r)

main()

Repair ideas

Revisestr2numFunction, try to convert to integer first, then convert to floating point number after failure:

def str2num(s):
    # 去掉字符串首尾的空格,避免 ' 123 ' 这样的问题
    s = s.strip()
    try:
        return int(s)
    except ValueError:
        return float(s)

Summarize

Python’s structured exception-handling is a core tool for writing reliable software:

  • It completely separates business logic and error handling
  • Call stack Traceback helps you quickly locate problems
  • Custom exceptions and delivery make error handling more flexible

Remember: Error handling is not to "cover errors", but to "handle errors gracefully, provide useful information, and ensure that the program runs within a controllable range".