Python 异常处理

本文面向已有前端开发基础、正在学习 Python 的开发者。

用前端开发中熟悉的 try...catchthrow、错误对象和接口错误处理,理解 Python 的异常处理。

异常处理的目的不是把错误藏起来,而是在程序运行到"不符合预期"的地方时,明确决定下一步怎么做。

如果你有 JavaScript / TypeScript 经验,可以先用这张表建立直觉:

Python 知识点 前端脑内映射 先记住
try try 包住可能出错的代码
except catch 出错后进入这里处理
except ValueError catch 里判断错误类型 只处理某一种错误
as e catch (error) 拿到错误对象
else JS 没有完全对应语法 try 没出错时执行
finally finally 不管成功失败都执行
raise throw 主动抛出错误
Exception Error 常见异常的通用父类
自定义异常 class XxxError extends Error 让错误类型更有业务含义
lua 复制代码
Python 异常处理

  发现错误
     |
     +-- Python 自动抛出异常
     +-- 自己用 raise 主动抛出异常
     |
  捕获错误
     |
     +-- try / except
     +-- except XxxError as e
     |
  处理结果
     |
     +-- else 成功后执行
     +-- finally 最后一定执行
     |
  工程表达
     |
     +-- 自定义异常
     +-- raise ... from e
     +-- traceback / 日志

先记一句话:

复制代码
异常 = 程序运行过程中,某一步无法继续正常执行

一、先建立总地图

学习阶段先熟悉这些常见异常:

异常类型 前端脑内映射 常见原因
NameError 变量没定义 使用了不存在的变量名
TypeError 类型错误 类型不支持当前操作
ValueError 值不合法 值的格式不对
KeyError 读对象上不存在的 key 字典里没有这个 key
IndexError 数组下标越界 列表下标越界
AttributeError null / 错误对象取属性 对象没有这个属性或方法
ZeroDivisionError 除法边界错误 除数是 0

从前端角度看,JS 里很多运行时问题最后都表现成 TypeError 或普通 Error。Python 会把错误分得更细,比如字典 key 不存在是 KeyError,列表下标越界是 IndexError

简单演示。下面每一行都是独立示例,不是让它们一次性全部运行:

python 复制代码
name # NameError,变量不存在

10 + '1' # TypeError,类型不支持当前操作

int('abc') # ValueError,值的格式不对

user = {}
user['name'] # KeyError,字典里没有这个 key

items = []
items[0] # IndexError,列表下标越界

value = None
value.strip() # AttributeError,对象没有这个方法

10 / 0 # ZeroDivisionError,除数是 0

二、try except

1. 前端脑内映射

Python 的 try...except 对应 JS 的 try...catch

把可能出错的代码放进 try,把出错后的处理放进 except

python 复制代码
try:
    age = int('abc')
except ValueError:
    print('年龄必须是数字')

对应 JS 里的写法大概是:

js 复制代码
try {
  const age = Number.parseInt('abc', 10);

  if (Number.isNaN(age)) {
    throw new Error('年龄必须是数字');
  }
} catch (error) {
  console.log(error.message);
}

差异在于:Python 的 int('abc') 会直接抛出 ValueError,而 JS 的 Number.parseInt('abc', 10) 会得到 NaN,你通常要自己判断。

2. 执行顺序

执行顺序:

rust 复制代码
try 里的代码正常
  -> 继续往下执行

try 里的某一行出错
  -> 后面的 try 代码不再执行
  -> 跳到匹配的 except

多个异常可以分别处理:

python 复制代码
data = {'count': 'abc'}

try:
    count = int(data['count'])
except KeyError:
    print('缺少 count 字段')
except ValueError:
    print('count 必须是数字')

这点和 JS 最大的区别是:Python 可以按异常类型分多个 except;JS 通常是在一个 catch 里自己判断错误类型。

3. 多个异常合并处理

多个异常处理逻辑一样,可以写成元组:

python 复制代码
try:
    count = int(data['count'])
except (KeyError, ValueError):
    print('count 参数不正确')

不要随手写空 except

python 复制代码
try:
    count = int(data['count'])
except:
    print('出错了')

这种写法会捕获太多错误,容易把真正的问题藏起来。

4. Exception 兜底放最后

如果需要兜底,写 Exception,并且放在最后:

python 复制代码
try:
    count = int(data['count'])
except KeyError:
    print('缺少 count')
except ValueError:
    print('count 必须是数字')
except Exception as e:
    print(f'未知异常:{e}')

多个 except 会从上往下匹配。越具体的异常越应该写在前面,越宽泛的异常越应该写在后面。

三、获取异常信息

1. 前端脑内映射

JS 里常见写法是 catch (error),Python 里对应的是 except XxxError as e

js 复制代码
try {
  throw new Error('年龄必须是数字');
} catch (error) {
  console.log(error.message);
  console.log(error.name);
}

2. Python 写法

as e 可以拿到异常对象。

python 复制代码
try:
    age = int('abc')
except ValueError as e:
    print(e) # 错误提示
    print(type(e).__name__) # 异常类型名称
    print(e.args) # 异常参数

常用信息:

写法 作用
str(e) 错误提示文本
type(e).__name__ 异常类型名称
e.args 异常参数

如果想看完整调用栈,用标准库 traceback

python 复制代码
import traceback

try:
    age = int('abc')
except ValueError:
    print(traceback.format_exc())

traceback 可以理解成 Python 里的 stack trace,能看到错误发生在哪个文件、哪一行、经过了哪些函数调用。

学习阶段可以用 print。真实项目里通常用日志记录,不要只留一句"出错了"。

四、else、finally 和 raise

1. try / except / else / finally

完整结构是:

rust 复制代码
try
  -> 尝试执行

except
  -> 出错时执行

else
  -> 没有出错时执行

finally
  -> 无论是否出错,最后都执行

前端熟悉的 try...catch...finally 在 Python 里多了一个常用分支:else

vbnet 复制代码
JS: try / catch / finally
Python: try / except / else / finally

例子:

python 复制代码
try:
    age = int('18')
except ValueError:
    print('年龄必须是数字')
else:
    print(f'转换成功:{age}')
finally:
    print('处理结束')

finally 常用于释放资源,比如关闭文件、关闭数据库连接、释放锁。

2. raise 主动抛出异常

有些错误不是 Python 自动发现的,而是你根据规则判断出来的,这时用 raise 主动抛出异常。

python 复制代码
def parse_age(value):
    age = int(value)

    if age < 0:
        raise ValueError('年龄不能小于 0')

    return age

对应 JS:

js 复制代码
function parseAge(value) {
  const age = Number(value);

  if (age < 0) {
    throw new Error('年龄不能小于 0');
  }

  return age;
}

调用方再决定怎么处理:

python 复制代码
try:
    age = parse_age('-1')
except ValueError as e:
    print(f'参数错误:{e}')

函数内部负责发现问题并 raise,函数外部再用 try except 决定怎么处理。

五、自定义异常和异常链

1. 自定义异常

内置异常能表达通用问题,比如 ValueError 表示值不合法。

如果你想表达更具体的业务含义,可以自定义异常。

前端里如果要区分不同错误,可能会写:

ts 复制代码
class UserNotFoundError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'UserNotFoundError';
  }
}

Python 里通常这样写:

python 复制代码
class UserError(Exception):
    """用户相关异常的基类。"""


class UserNotFoundError(UserError):
    """用户不存在。"""

使用:

python 复制代码
def find_user(user_id):
    users = {1: 'Tom', 2: 'Jerry'}

    if user_id not in users:
        raise UserNotFoundError(f'用户不存在:{user_id}')

    return users[user_id]

捕获:

python 复制代码
try:
    name = find_user(3)
except UserNotFoundError as e:
    print(e)

自定义异常的好处是:异常类型本身就能表达含义。

rust 复制代码
ValueError
  -> 只能知道"值不对"

UserNotFoundError
  -> 一眼知道"用户不存在"

如果一组异常属于同一类,可以定义共同父类:

python 复制代码
class AppError(Exception):
    """项目异常基类。"""


class ConfigError(AppError):
    """配置错误。"""


class AppPermissionError(AppError):
    """权限错误。"""

这样可以统一捕获:

python 复制代码
try:
    raise ConfigError('缺少数据库配置')
except AppError as e:
    print(f'应用异常:{e}')

2. raise from 保留异常链

有时底层错误比较细,直接暴露给上层不够清楚。可以用 raise ... from e 转换异常,同时保留原始原因。

python 复制代码
class AgeError(Exception):
    """年龄参数错误。"""


def parse_age(value):
    try:
        return int(value)
    except ValueError as e:
        raise AgeError('年龄必须是数字') from e

含义:

arduino 复制代码
表层原因:AgeError,年龄必须是数字
底层原因:ValueError,int 转换失败

这样既能给上层一个清楚的异常类型,又不会丢掉原始错误。

六、实践原则

异常处理最容易出问题的地方,不是语法,而是捕获范围太大、处理太随意。

从前端经验迁移过来,可以这样理解:

arduino 复制代码
不要到处 catch 后什么都不做
不要把所有错误都变成一个"系统异常"
不要给用户暴露完整 stack trace
不要把开发排查需要的信息丢掉
  • 不要随手写空 except:
  • 不要把所有代码都包进一个巨大的 try
  • 能捕获具体异常,就不要一开始捕获 Exception
  • Exception 兜底要放在最后
  • 捕获异常后要处理、记录,或者继续抛出,不要悄悄吞掉
  • 自定义异常通常继承 Exception
  • 函数内部可以只负责 raise,外层再统一 try except
  • 给用户看的提示要简洁,给开发看的错误信息要完整
相关推荐
sugar__salt1 小时前
从栈队列数据结构到JS原型面向对象全解
前端·javascript·数据结构
独特的螺狮粉2 小时前
篮球集训班器具管理系统 - 鸿蒙PC Electron框架完整技术实现指南
前端·javascript·华为·electron·前端框架·开源·鸿蒙
pusheng20252 小时前
IFSJ全英文专访:中国创新力量重塑先进气体感知技术,赋能全球关键基础设施安全
前端·网络·人工智能·物联网·安全
麻雀飞吧2 小时前
期货多合约策略目标持仓怎么更新才不乱
python·区块链
Cthy_hy2 小时前
拓扑排序超详解:原理 + Kahn 贪心算法
python·算法·贪心算法
LSssT.2 小时前
【01】Python 机器学习
开发语言·python
AI_零食2 小时前
番茄钟鸿蒙PC Electron框架完成:状态机、定时器管理与专注力工具设计
前端·javascript·华为·electron·开源·鸿蒙·鸿蒙系统
为爱停留2 小时前
给智能体装上「刹车」:中断(Interrupts)与人工审批全解析
python
提子拌饭1332 小时前
逛三园游戏——基于鸿蒙PC Electron框架实现
前端·javascript·游戏·华为·electron·鸿蒙