讲解

程序运行中出错时,Python 会「抛出异常」:int('abc') 抛 ValueError、打开不存在的文件抛 FileNotFoundError、除以零抛 ZeroDivisionError、访问不存在的键抛 KeyError。没人处理的异常会一直向上传播,最终让程序崩溃并打印 traceback(错误堆栈)。traceback 从下往上读:最后一行是异常类型和信息,往上是调用链,学会读它是调试的基本功。

try/except 是处理异常的结构:把可能出错的代码放进 try,出错后跳转匹配的 except 分支执行,程序不再崩溃。except 后面要写具体的异常类型——except ValueError: 只抓值错误;裸 except: 或 except Exception: 会把所有错误(包括你没想到的 bug)都吞掉,是明确反对的写法。完整形态还有 else(try 没出错时执行)和 finally(出不出错都执行,做清理):try 里只放真正可能抛错的最小代码块,块越大越容易误抓。

主动抛出异常用 raise:raise ValueError('年龄不能为负')。标准库定义了丰富的异常类型,自定义异常写一个继承 Exception 的空类即可:class ConfigError(Exception): pass。Python 的错误处理哲学是 EAFP——「先做再说,出事道歉」:与其 if 一串检查再动手(LBYL),不如直接做、用 try 兜住异常,这在文件、网络等外部资源场景尤其合理,因为检查和执行之间状态可能已经变了。

示例

# 基本形态:try / except / else / finally
def parse_age(text):
    try:
        age = int(text)
    except ValueError:
        print(f'{text!r} 不是有效数字')
        return None
    else:
        print(f'解析成功:{age}')
        return age
    finally:
        print('(finally:无论成败都会执行)')


parse_age('18')
print('---')
parse_age('abc')

# 捕获多种异常 + 拿到异常对象
def read_number(path):
    try:
        with open(path, 'r', encoding='utf-8') as f:
            return int(f.read().strip())
    except FileNotFoundError:
        print(f'文件 {path} 不存在')
    except ValueError as exc:
        print(f'文件内容不是数字:{exc}')
    return None


print('读不存在的文件:', read_number('不存在的文件.txt'))

# 主动抛出异常
def set_age(age):
    if age < 0:
        raise ValueError(f'年龄不能为负:{age}')
    return age


try:
    set_age(-3)
except ValueError as exc:
    print('抓到主动抛出的异常:', exc)

# 自定义异常
class ConfigError(Exception):
    """配置相关错误。"""


def load_config(data):
    if 'host' not in data:
        raise ConfigError('缺少必需的 host 字段')
    return data


try:
    load_config({'port': 8080})
except ConfigError as exc:
    print('配置错误:', exc)

# EAFP 风格:直接做,出事再兜
d = {'a': 1}
try:
    print('EAFP 取值:', d['b'])
except KeyError:
    print('键不存在,走兜底逻辑')

常见坑

  • 裸 except 吞掉一切:except: 连 KeyboardInterrupt 和真正的 bug 一起吞,问题被掩盖到无法排查。永远写具体类型。
  • try 块太大:把十行代码全塞进 try,抛错时根本不知道是哪一行出的问题。try 里只放可能抛错的那一两行。
  • except 里只写 pass:「出错了就当无事发生」比崩溃更可怕——数据悄悄错了你还不知道。至少要打印日志。
  • 用异常控制正常流程:拿 KeyError 当 if 用偶尔是 EAFP 的合理写法,但循环里依赖 ZeroDivisionError 之类做分支就是设计失误了。

小结

try/except/else/finally 抓具体异常,raise 主动抛,自定义异常继承 Exception,哲学是 EAFP。下一节进入面向对象:类与对象。