05-Java工程师的Python第五课-异常处理

2 阅读6分钟

从Java异常到Python异常:try-except的正确姿势

摘要:Java有checked异常和unchecked异常之分,Python只有unchecked异常但设计更简洁。


写在前面

Java的异常体系以"checked异常"著称,这是Java区别于大多数语言的独特设计。Python的异常体系更接近C#和JavaScript,简单直接。

对Java工程师来说,最大的挑战是:忘掉checked异常的概念。Python没有checked异常,所有异常都是unchecked。


一、异常体系对比

1.1 Java的异常体系

Throwable
├── Error (unchecked)
│   ├── OutOfMemoryError
│   ├── StackOverflowError
│   └── ...
└── Exception (checked + unchecked)
    ├── IOException (checked) ← 必须声明或捕获
    ├── SQLException (checked) ← 必须声明或捕获
    └── RuntimeException (unchecked)
        ├── NullPointerException
        ├── IllegalArgumentException
        └── ...

1.2 Python的异常体系

BaseException
├── SystemExit
├── KeyboardInterrupt
└── Exception (常规异常都在这里)
    ├── TypeError
    ├── ValueError
    ├── KeyError
    ├── IndexError
    ├── AttributeError
    ├── ImportError
    ├── OSError
    └── ... (没有checked vs unchecked区分)

二、基本语法对比

2.1 try-catch-finally

// Java - catch顺序:子类在前,父类在后
try {
    int result = 10 / 0;
} catch (ArithmeticException e) {
    System.out.println("除零错误: " + e.getMessage());
} catch (Exception e) {
    System.out.println("其他错误: " + e.getMessage());
} finally {
    System.out.println("总是执行");
}
# Python - except顺序同样:子类在前,父类在后
try:
    result = 10 / 0
except ZeroDivisionError as e:
    print(f"除零错误: {e}")
except Exception as e:
    print(f"其他错误: {e}")
finally:
    print("总是执行")

2.2 关键差异:异常声明

// Java - checked异常必须声明或捕获
public void readFile() throws IOException {
    BufferedReader reader = new BufferedReader(
        new FileReader("file.txt")
    );
    String line = reader.readLine();
    reader.close();
}

// 调用者必须处理
try {
    readFile();
} catch (IOException e) {
    e.printStackTrace();
}
# Python - 无需声明,调用者自行决定是否捕获
def read_file():
    with open("file.txt") as f:
        return f.readline()

# 调用者可以捕获,也可以不捕获
try:
    content = read_file()
except FileNotFoundError:
    content = ""

三、抛出异常对比

3.1 基本语法

// Java
throw new IllegalArgumentException("参数不能为空");
throw new RuntimeException("未知错误");
# Python
raise ValueError("参数不能为空")
raise RuntimeError("未知错误")

# raise也可以单独使用(重新抛出当前异常)
try:
    do_something()
except:
    print("记录日志")
    raise  # 重新抛出

3.2 自定义异常

// Java - 通常继承Exception或RuntimeException
public class BusinessException extends RuntimeException {
    private final String code;

    public BusinessException(String code, String message) {
        super(message);
        this.code = code;
    }

    public String getCode() {
        return code;
    }
}

// 抛出
throw new BusinessException("USER_NOT_FOUND", "用户不存在");
# Python
class BusinessException(Exception):
    def __init__(self, code: str, message: str):
        super().__init__(message)
        self.code = code

# 抛出
raise BusinessException("USER_NOT_FOUND", "用户不存在")

四、异常捕获对比

4.1 基本捕获

// Java
try {
    process();
} catch (IOException e) {
    log.error("IO错误", e);
} finally {
    cleanup();
}
# Python - except子句更灵活
try:
    process()
except IOError as e:  # 可以 as 一个变量名
    log.error(f"IO错误: {e}")
except (ValueError, TypeError):  # 可以同时捕获多种异常
    pass  # 静默忽略
finally:
    cleanup()

4.2 Python特有的捕获方式

# 捕获所有异常(不推荐)
try:
    risky_operation()
except:
    pass  # 捕获所有异常,但不记录是什么错误

# 正确做法:至少记录异常或保留traceback
try:
    risky_operation()
except Exception as e:
    print(f"发生错误: {e}")
    import traceback
    traceback.print_exc()

# Python 3.11+ 的异常组(Exception Group)
try:
    raise ExceptionGroup("errors", [ValueError(), TypeError()])
except* ValueError:
    print("处理ValueError")
except* TypeError:
    print("处理TypeError")

五、with语句与资源管理

5.1 Java的try-with-resources

// Java 7+ try-with-resources
try (BufferedReader reader = new BufferedReader(
        new FileReader("file.txt"))) {
    String line = reader.readLine();
    System.out.println(line);
} // 自动关闭,无需finally

// 多个资源
try (
    Connection conn = DriverManager.getConnection(url);
    PreparedStatement stmt = conn.prepareStatement(sql);
) {
    // 使用conn和stmt
}

5.2 Python的with语句

# Python的with语句更简洁
with open("file.txt") as f:
    content = f.read()
# 自动关闭文件

# 多个上下文管理器
with open("input.txt") as f1, open("output.txt", "w") as f2:
    f2.write(f1.read())

# Python 3.10+ 更优雅的写法
with (
    open("input.txt") as f1,
    open("output.txt", "w") as f2
):
    f2.write(f1.read())

5.3 自定义上下文管理器

// Java - 实现AutoCloseable接口
public class ResourceManager implements AutoCloseable {
    public void use() {
        System.out.println("使用资源");
    }

    @Override
    public void close() {
        System.out.println("释放资源");
    }
}

// 使用
try (ResourceManager rm = new ResourceManager()) {
    rm.use();
}
# Python - 实现__enter__和__exit__或使用contextmanager
class ResourceManager:
    def __enter__(self):
        print("获取资源")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("释放资源")
        return False  # 返回True会压制异常

with ResourceManager() as rm:
    print("使用资源")

# 更简洁的方式:使用装饰器
from contextlib import contextmanager

@contextmanager
def resource_manager():
    print("获取资源")
    try:
        yield
    finally:
        print("释放资源")

with resource_manager():
    print("使用资源")

六、Python标准异常一览

6.1 常用异常类

Python异常类似于Java使用场景
ValueErrorIllegalArgumentException值不合法
TypeError无对应类型不匹配
KeyErrorNoSuchElementExceptiondict键不存在
IndexErrorIndexOutOfBoundsException列表索引越界
AttributeErrorNoSuchMethodException属性不存在
FileNotFoundErrorFileNotFoundException文件不存在
RuntimeErrorRuntimeException运行时错误
NotImplementedError无直接对应方法未实现

6.2 抛出建议

# ✅ 推荐:抛出具体异常
def sqrt(n):
    if n < 0:
        raise ValueError("不能计算负数的平方根")
    return n ** 0.5

# ❌ 不推荐:抛出通用异常
def sqrt(n):
    if n < 0:
        raise Exception("错误")  # 太宽泛
    return n ** 0.5

# ✅ 推荐:选择最合适的异常类型
# 如果标准库有对应的异常,优先使用
if key not in mapping:
    raise KeyError(key)  # 而非ValueError

七、异常处理最佳实践

7.1 Java最佳实践

// 1. 不要吞掉异常
try {
    doSomething();
} catch (IOException e) {
    // ❌ 错误:什么都不做
}

// 正确做法:记录或重新抛出
try {
    doSomething();
} catch (IOException e) {
    log.error("IO错误", e);
    throw new RuntimeException("操作失败", e);
}

// 2. 不要捕获所有异常
try {
    doSomething();
} catch (Exception e) {  // 太宽泛
    // ...
}

7.2 Python最佳实践

# 1. 不要使用裸except
try:
    do_something()
except:  # ❌ 捕获所有异常,包括SystemExit
    pass

# 正确做法
try:
    do_something()
except Exception as e:  # ✅ 明确捕获Exception
    log.error(f"错误: {e}")

# 2. except子句要具体
try:
    data = json.loads(user_input)
except json.JSONDecodeError as e:
    return f"JSON格式错误: {e}"
except (KeyError, IndexError) as e:
    return f"数据格式错误: {e}"

# 3. 使用else子句(try成功时执行)
try:
    result = int(input_str)
except ValueError:
    print("不是有效的数字")
else:
    print(f"成功转换: {result}")  # 仅在try成功时执行

八、异常链与原因

8.1 Java的异常链

// Java - 支持cause
try {
    readFile();
} catch (IOException e) {
    throw new BusinessException("READ_ERROR", "读取失败", e);
}

// 获取cause
try {
    doSomething();
} catch (BusinessException e) {
    IOException cause = (IOException) e.getCause();
}

8.2 Python的异常链

# Python 3 - 支持from关键字
try:
    read_file()
except FileNotFoundError as e:
    raise BusinessError("文件不存在") from e

# 查看异常链
try:
    raise ValueError("原始错误")
except ValueError as e:
    raise RuntimeError("新的错误") from e

# __cause__ 显示显式异常链
# __context__ 显示隐式异常链(异常传播时自动设置)

# 查看异常链信息
try:
    1/0
except Exception as e:
    print(f"异常: {e}")
    print(f"原因: {e.__cause__}")
    print(f"上下文: {e.__context__}")

九、实战:统一错误处理

9.1 Java的全局异常处理

// Spring框架的全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    public Result handleBusiness(BusinessException e) {
        return Result.error(e.getCode(), e.getMessage());
    }

    @ExceptionHandler(Exception.class)
    public Result handleGeneral(Exception e) {
        log.error("系统错误", e);
        return Result.error("SYSTEM_ERROR", "系统错误");
    }
}

9.2 Python的全局异常处理

# Flask风格
from flask import Flask, jsonify

app = Flask(__name__)

@app.errorhandler(BusinessError)
def handle_business(e):
    return jsonify({"code": e.code, "message": e.message}), 400

@app.errorhandler(Exception)
def handle_general(e):
    app.logger.error("系统错误", exc_info=True)
    return jsonify({"code": "SYSTEM_ERROR", "message": "系统错误"}), 500

# 或者用装饰器
def handle_errors(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        try:
            return f(*args, **kwargs)
        except BusinessError as e:
            return jsonify({"code": e.code, "message": e.message}), 400
        except Exception as e:
            app.logger.error("系统错误", exc_info=True)
            return jsonify({"code": "SYSTEM_ERROR", "message": "系统错误"}), 500
    return wrapper

十、总结

特性JavaPython
Checked异常
声明throws必须可选
finally必须配合try独立使用
资源自动关闭try-with-resourceswith语句
异常链initCause() / 构造器raise E() from e
异常组ExceptionGroup(3.11+)
重新抛出throw eraise

Python的异常处理比Java更简洁,没有checked异常的负担。但这也意味着调用者必须更加主动地去处理可能的异常。建议养成"捕获具体异常、记录日志、必要时重新抛出"的好习惯。