错误处理

Python 错误处理机制详解

错误处理的基本概念

在程序运行过程中,错误是不可避免的。传统的错误处理方式是使用错误码,但这种方式存在明显缺陷:

  1. 正常结果和错误码混在一起,难以区分
  2. 需要大量代码判断错误
  3. 错误需要逐级上报,代码冗长

Python 提供了更优雅的错误处理机制:try...except...finally...

try-except 基本用法

try:
    # 可能出错的代码
    print('try...')
    r = 10 / 0
    print('result:', r)
except ZeroDivisionError as e:
    # 处理特定错误
    print('except:', e)
finally:
    # 无论是否出错都会执行
    print('finally...')
print('END')

执行流程:

  1. 先执行 try 块中的代码
  2. 如果出错,跳过后续代码,执行对应的 except
  3. 最后执行 finally 块(如果有)
  4. 程序继续正常执行

多异常处理

可以捕获多种不同类型的异常:

try:
    print('try...')
    r = 10 / int('a')
    print('result:', r)
except ValueError as e:
    print('ValueError:', e)
except ZeroDivisionError as e:
    print('ZeroDivisionError:', e)
finally:
    print('finally...')
print('END')

else 子句

当没有错误发生时,可以执行 else 块:

try:
    print('try...')
    r = 10 / int('2')
    print('result:', r)
except ValueError as e:
    print('ValueError:', e)
except ZeroDivisionError as e:
    print('ZeroDivisionError:', e)
else:
    print('no error!')
finally:
    print('finally...')
print('END')

异常继承关系

Python 的所有异常都继承自 BaseException。常见的异常类型包括:

  • Exception - 常规错误的基类
  • ArithmeticError - 算术错误的基类
  • ZeroDivisionError - 除零错误
  • ValueError - 值错误
  • TypeError - 类型错误
  • IOError - 输入输出错误

完整的异常继承关系参考 Python 官方文档

调用栈分析

当错误未被捕获时,Python 会打印调用栈信息:

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

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

def main():
    bar('0')

main()

输出示例:

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

解读方法:

  1. 从下往上查看错误信息
  2. 最后一行是错误类型和原因
  3. 上面各行显示了错误的调用链

记录错误日志

使用 logging 模块可以记录错误信息而不中断程序:

import logging

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(e)

main()
print('END')

自定义异常

可以定义自己的异常类型:

class FooError(ValueError):
    pass

def foo(s):
    n = int(s)
    if n == 0:
        raise FooError(f'invalid value: {s}')
    return 10 / n

foo('0')

最佳实践:

  1. 优先使用内置异常类型
  2. 只在必要时定义自定义异常
  3. 保持异常继承关系合理

异常传递

可以在捕获异常后重新抛出:

def foo(s):
    n = int(s)
    if n == 0:
        raise ValueError(f'invalid value: {s}')
    return 10 / n

def bar():
    try:
        foo('0')
    except ValueError as e:
        print('ValueError!')
        raise  # 重新抛出异常

bar()

这种模式常用于:

  1. 记录错误日志
  2. 转换错误类型
  3. 在高层统一处理错误

错误处理最佳实践

  1. 精确捕获:只捕获你知道如何处理的异常
  2. 避免空except:不要使用空的 except: 捕获所有异常
  3. 保持简洁try 块中只包含可能出错的代码
  4. 提供上下文:抛出异常时包含有用的错误信息
  5. 文档说明:在函数文档中说明可能抛出的异常

练习:修复错误

from functools import reduce

def str2num(s):
    try:
        return int(s)
    except ValueError:
        return float(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')
    print('99 + 88 + 7.6 =', r)

main()

修复说明:

  1. 原代码无法处理浮点数
  2. 修改 str2num 函数,先尝试转换为整数,失败后再尝试浮点数

总结

Python 的错误处理机制提供了强大而灵活的方式来处理程序中的异常情况。合理使用 try-except 可以:

  1. 使代码更健壮
  2. 错误处理更清晰
  3. 避免程序意外终止
  4. 提供更好的错误诊断信息

记住:良好的错误处理是编写可靠软件的关键部分。