0.9 错误处理与调试

概述

你已经熟悉 JS 的 try/catch/finallythrow、自定义 Error、浏览器的 DevTools 调试器,以及 console.log。Python 的错误处理语法类似但更丰富(多了 else、异常层级更细),调试工具有 pdb,日志有标准库 logging。这一章把错误处理和调试的完整流程对齐。

JS/TS ↔ Python 对照表

概念

JavaScript

Python

说明

捕获异常

try / catch

try / except

catchexcept

捕获特定异常

catch (TypeError e)

except TypeError as e:

都支持按类型捕获

捕获多个

catch (A | B)(较新)

except (A, B) as e:

元组形式

捕获所有

catch (e)

except Exception as e: / 裸 except

建议用 Exception

finally

finally

finally

语义一致

异常后成功执行

无(手动标记)

else

Python 独有

抛出异常

throw new Error("x")

raise ValueError("x")

throwraise

自定义异常

class X extends Error

class X(Exception):

继承基类

重新抛出

throw e;

raise(不带参数)

raise 重抛当前异常

获取异常信息

e.message / e.stack

str(e) / traceback

需 traceback 拿堆栈

断言

console.assert / 手写

assert cond, "msg"

Python 内置关键字

调试器

浏览器 DevTools / debugger

pdb / breakpoint()

见下文

日志

console.log/warn/error

logging 模块

Python 有分级日志

错误监控

Sentry

Sentry(同一套)

一致

常见错误

TypeErrorReferenceError

TypeErrorNameError

名称不同

空值错误

TypeError: Cannot read ... of undefined

AttributeError / KeyError

对应关系见下文

核心概念

1. try/except/else/finally vs try/catch/finally

// JS
try {
  risky();
} catch (e) {
  handle(e);
} finally {
  cleanup();
}
# Python
try:
    risky()
except SomeError as e:
    handle(e)
else:
    # 仅当 try 块没有抛出异常时执行(JS 没有)
    success()
finally:
    cleanup()
  • catchexceptas e 绑定异常对象。

  • Python 多了 else 子句:try 无异常时执行(常用来放"只有成功才做"的代码,避免被 except 误捕获)。

  • finally 语义完全一致,总是执行。

2. raise vs throw

// JS
if (x < 0) {
  throw new Error("x 不能为负");
}
# Python
if x < 0:
    raise ValueError("x 不能为负")
  • throwraise,且通常抛出特定异常类型ValueErrorTypeError 等),比 JS 里清一色 Error 更精细。

3. 捕获特定异常

try {
  JSON.parse(str);
} catch (e) {
  if (e instanceof SyntaxError) {
    // ...
  }
}
try:
    import json
    json.loads(s)
except json.JSONDecodeError as e:
    print("JSON 格式错误")

Python 的异常层级更丰富,鼓励按类型捕获:

try:
    value = data["key"]        # 可能 KeyError
    num = int(value)           # 可能 ValueError
except KeyError:
    print("键不存在")
except ValueError:
    print("值不是数字")
except (TypeError, KeyError) as e:   # 捕获多个
    print("类型或键错误")

4. 自定义异常类

// JS
class ValidationError extends Error {
  constructor(message, field) {
    super(message);
    this.name = "ValidationError";
    this.field = field;
  }
}
# Python
class ValidationError(Exception):
    def __init__(self, message, field):
        super().__init__(message)
        self.field = field

raise ValidationError("字段非法", field="email")
  • 继承 Exception(而非 Error,Python 的 ErrorException 的子类)。

  • 业务错误建议自定义异常,便于上层按类型捕获。

5. 常见异常类型对照

JavaScript

Python

触发场景

TypeError

TypeError

类型错误(如 1 + "a"

ReferenceError

NameError

访问未定义变量

SyntaxError

SyntaxError

语法错误

RangeError

IndexError

数组/序列越界

对象无属性

AttributeError

obj.missing 访问不存在属性

对象无键

KeyError

d["missing"] 字典键不存在

URIError / 编码

UnicodeDecodeError

编码解码失败

RangeError(递归)

RecursionError

递归过深

手动 import 失败

ImportError

导入模块失败

Error(基础类)

Exception(基础类)

捕获所有异常的兜底

6. pdb 调试器 vs 浏览器 debugger

JS 里你可以在浏览器 DevTools 打断点、单步执行、查看调用栈。Python 的等价物是 pdb(Python Debugger):

// JS:代码里加断点
debugger;
# Python 3.7+:breakpoint() 是内置断点
def buggy(a, b):
    breakpoint()     # 程序停在这里,进入 pdb 交互
    return a / b

pdb 常用命令(类似 DevTools 的按钮):

DevTools

pdb 命令

说明

继续执行

c / continue

跳到下一个断点

单步进入

s / step

进入函数

单步跳过

n / next

执行下一行

查看变量

p <expr>

打印表达式

调用栈

w / where

显示堆栈

退出

q / quit

退出调试器

7. logging vs console

console.log 在 Python 里对应 print,但生产级应用应该用标准库 logging(分级日志):

// JS
console.log("信息");
console.warn("警告");
console.error("错误");
# Python
import logging

logging.basicConfig(level=logging.INFO)
logging.debug("调试信息")    # 默认不输出(低于 INFO 级别)
logging.info("信息")
logging.warning("警告")
logging.error("错误")
  • 日志级别从低到高:DEBUG < INFO < WARNING < ERROR < CRITICAL

  • 可配置输出到文件、控制台、远端,带时间戳、级别、模块名,比 print 专业得多。

8. 断言 assert

// JS
console.assert(x > 0, "x 必须为正");
# Python
assert x > 0, "x 必须为正"
  • assert 是 Python 关键字,条件为假时抛 AssertionError

  • 注意:python -O(优化模式)会跳过所有 assert,所以别用 assert 做业务校验,只用于开发期自检。

代码示例

完整的异常处理流程

// JS
function parseConfig(raw) {
  try {
    const cfg = JSON.parse(raw);
    if (!cfg.url) throw new Error("缺少 url");
    return cfg;
  } catch (e) {
    console.error("解析失败:", e.message);
    return null;
  } finally {
    console.log("解析结束");
  }
}
# Python
import json
import logging

def parse_config(raw):
    try:
        cfg = json.loads(raw)
    except json.JSONDecodeError as e:
        logging.error("JSON 格式错误: %s", e)
        return None
    except ValueError as e:
        logging.error("解析失败: %s", e)
        return None
    else:
        if "url" not in cfg:
            raise KeyError("缺少 url")
        return cfg
    finally:
        logging.info("解析结束")

自定义异常 + 特定捕获

// JS
class HttpError extends Error {
  constructor(status) { super(`HTTP ${status}`); this.status = status; }
}
try { throw new HttpError(404); } catch (e) {
  if (e instanceof HttpError && e.status === 404) { /* ... */ }
}
# Python
class HttpError(Exception):
    def __init__(self, status):
        super().__init__(f"HTTP {status}")
        self.status = status

try:
    raise HttpError(404)
except HttpError as e:
    if e.status == 404:
        ...

最佳实践

  • 捕获具体异常类型,别一上来就 except Exception 兜底(会掩盖 bug,类似 JS 里吞掉所有异常)。

  • 抛出异常用 raise ValueError("明确信息"),选对异常类型,别全用裸 Exception

  • else 子句放"成功路径"代码,避免把成功逻辑写进 try 又没异常时被误捕获。

  • 生产日志用 logging(分级、可配置),别用 print

  • 调试用 breakpoint()(Python 3.7+),它比 pdb.set_trace() 更简洁且可被环境变量关闭。

  • assert 只用于开发期自检,不用于运行时校验(-O 会跳过)。

  • 给外部数据(API、文件)做校验,考虑用 pydantic,而不是满屏 try/except。

前端开发者常见陷阱

  1. catch 而非 excepttry/except,不是 try/catch,写错直接 SyntaxError

  2. except Exception 吞掉一切:包含 KeyboardInterrupt(Ctrl+C 也能被吞)等系统异常,正确的兜底是 except Exception,裸 except: 会连系统退出都吞。

  3. 重新抛出用 raise 而非 raise eraise(不带参数)保留原始堆栈,raise e 会重置 traceback,调试更难。

  4. elsefinally 顺序:顺序是 try → except → else → finally,写反报错。

  5. assert 做参数校验-O 模式会跳过 assert,导致校验失效。

  6. 异常对象没有 .message:Python 用 str(e),堆栈要 import traceback; traceback.format_exc()

  7. KeyError vs AttributeError 搞混:字典缺键是 KeyError,对象缺属性是 AttributeError,别混用。

  8. console.errorlogging.error:Python 的 logging 需要先 basicConfig 配置,否则默认可能不输出到控制台(级别默认 WARNING)。

  9. finally 里 return 覆盖异常finallyreturn 会吞掉原异常/返回值(JS 也同理),避免在 finally 里 return。

  10. 忽略 breakpoint() 的调用栈:pdb 交互式命令和 DevTools 按钮不同,先学 n/s/c/p 四个命令就够用。

总结

  • try/except/else/finally 对应 try/catch/finallyexcept 代替 catch,多了 else

  • throwraise,自定义异常继承 Exception,异常层级比 JS 更细。

  • Python 用 breakpoint() + pdb 调试(对应 debugger + DevTools)。

  • 生产日志用 logging(分级),替代 console.logassert 仅用于开发自检。

  • 按异常类型捕获、用 raise 重抛保留堆栈、避免 except Exception 兜底,是 Python 错误处理的核心原则。