在 C# 里写异常处理,try-catch-finally 三件套,捕获类型精确到 InvalidOperationException,连 when 条件都能加。异常体系严谨得像一部法典------SystemException、ApplicationException、CustomException,层级分明。
在 Python 里写异常处理,try-except 两件套搞定,没有 finally(其实有但很少用),捕获类型直接写类名,没有 throw,没有 ExceptionDispatcher。简单得像在纸条上写"出问题了就执行这段"。
当我第一次在 Python 里写 except Exception as e 的时候,我的内心是:"这就完了?我的 catch (Exception ex) 呢?我的 when 呢?我的 ExceptionDispatchInfo 呢?"
后来我才明白,Python 的异常处理哲学是:别把简单的事情搞复杂 。而 C# 的异常处理哲学是:每种错误都值得被精确对待。
两种语言的异常处理哲学完全不同------一个追求精确,一个追求简洁。
基础语法对比
C# 版本:
try
{
int result = 10 / int.Parse("0");
Console.WriteLine(result);
}
catch (DivideByZeroException ex)
{
Console.WriteLine($"除零错误: {ex.Message}");
}
catch (FormatException ex)
{
Console.WriteLine($"格式错误: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"其他错误: {ex.Message}");
}
finally
{
Console.WriteLine("不管出不出错都执行");
}
Python 版本:
try:
result = 10 / int("0")
print(result)
except ZeroDivisionError as e:
print(f"除零错误: {e}")
except ValueError as e:
print(f"格式错误: {e}")
except Exception as e:
print(f"其他错误: {e}")
finally:
print("不管出不出错都执行")
对比一下:
| 对比项 | C# | Python |
|---|---|---|
| 关键字 | catch |
except |
| 捕获变量 | catch (Exception ex) |
except Exception as e |
| 多类型捕获 | `catch (AException | BException ex)` |
| 异常过滤器 | catch (Exception ex) when (...) |
except Exception as e: 里手动判断 |
| finally | 必须有 | 可选 |
| throw | 必须用 throw |
直接 raise |
| 重新抛出 | throw;(保留堆栈) |
raise(保留堆栈) |
C# 的 catch 像在签合同,每种异常都有对应的条款;Python 的 except 像在贴纸条,"出问题了就执行这段"。
没有 finally?Python 有更优雅的方案
C# 的 finally 用来做资源清理------关闭文件、释放连接、解锁。Python 也有 finally,但更常用的是 with 语句:
C# 版本:
StreamReader reader = null;
try
{
reader = new StreamReader("file.txt");
string content = reader.ReadToEnd();
Console.WriteLine(content);
}
finally
{
reader?.Dispose();
}
Python 版本:
with open("file.txt") as f:
content = f.read()
print(content)
# 离开 with 块自动关闭,不需要 finally
with 语句自动调用 __exit__ 方法来清理资源,比 finally 更简洁。C# 其实也有类似的 using 语句:
using (var reader = new StreamReader("file.txt"))
{
string content = reader.ReadToEnd();
Console.WriteLine(content);
}
// 自动 Dispose
设计哲学几乎一样------用语法糖来保证资源清理 ,只是 Python 叫 with,C# 叫 using。
异常捕获:精确 vs 宽松
C# 的异常捕获非常精确,连 when 条件都能加:
try
{
await httpClient.GetAsync(url);
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
Console.WriteLine("页面不存在");
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized)
{
Console.WriteLine("未授权");
}
catch (HttpRequestException ex)
{
Console.WriteLine($"请求失败: {ex.StatusCode}");
}
Python 的异常捕获没有 when,但可以用 if 判断:
try:
response = requests.get(url)
response.raise_for_status()
except requests.exceptions.HTTPError as e:
if e.response.status_code == 404:
print("页面不存在")
elif e.response.status_code == 401:
print("未授权")
else:
print(f"请求失败: {e.response.status_code}")
C# 用 when 在 catch 里过滤,Python 用 if 在 except 里过滤。效果一样,但 C# 的 when 更"声明式",Python 的 if 更"命令式"。
多类型捕获
C# 可以用 | 在一个 catch 里捕获多种异常:
try
{
// ...
}
catch (InvalidOperationException | ArgumentException ex)
{
Console.WriteLine($"参数或状态错误: {ex.Message}");
}
Python 也可以:
try:
# ...
except (ValueError, TypeError) as e:
print(f"值或类型错误: {e}")
几乎一模一样,都是"这两种异常我用同一种方式处理"。
异常的层级结构
C# 的异常层级:
System.Exception
├── System.SystemException
│ ├── System.DivideByZeroException
│ ├── System.NullReferenceException
│ ├── System.IndexOutOfRangeException
│ └── ...
├── System.ApplicationException
└── 自定义异常
Python 的异常层级:
BaseException
├── SystemExit
├── KeyboardInterrupt
├── GeneratorExit
├── Exception
│ ├── ValueError
│ ├── TypeError
│ ├── KeyError
│ ├── IndexError
│ ├── FileNotFoundError
│ └── ...
└── 自定义异常
关键区别:
| 特性 | C# | Python |
|---|---|---|
| 基类 | System.Exception |
Exception(更上面还有 BaseException) |
| 需要继承吗 | 建议继承 Exception |
可以继承任意类 |
| 编译检查 | 捕获未声明的异常会警告 | 不检查,运行时才知道 |
| checked/unchecked | 有 checked 关键字 |
没有,全是 unchecked |
Python 的异常不需要显式声明,函数可以抛出任何异常,编译器不会告诉你。这是 Python 的"信任程序员"哲学------你觉得需要检查就检查,不检查也行。
raise vs throw
C# 抛异常必须用 throw:
throw new ArgumentNullException(nameof(name));
throw; // 重新抛出当前异常
Python 抛异常用 raise:
raise ValueError("name 不能为空")
raise # 重新抛出当前异常
还有一个区别:C# 的 throw 会重置堆栈跟踪,throw; 不会。Python 的 raise 始终保留堆栈跟踪。
上下文管理器:with 语句
Python 独有的 with 语句,是异常处理的最佳拍档:
class FileManager:
def __init__(self, filename):
self.filename = filename
def __enter__(self):
self.file = open(self.filename, 'r')
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
self.file.close()
if exc_type is not None:
print(f"发生错误: {exc_val}")
return False # 不抑制异常
with FileManager("file.txt") as f:
content = f.read()
__exit__ 方法接收三个参数:
-
exc_type:异常类型(没有异常时为None) -
exc_val:异常值 -
exc_tb:异常堆栈
返回 True 会抑制异常(吞掉),返回 False 会继续抛出。
C# 的 IDisposable 和 using 实现类似的功能,但没有这么灵活------using 只负责调用 Dispose(),不能在 Dispose 里判断是否有异常。
异常链:Exception Dispatch Info vs Exception Chaining
C# 保留原始异常的堆栈:
try
{
// ...
}
catch (Exception ex)
{
throw new ApplicationException("处理失败", ex); // 保留内部异常
}
// 或者用 ExceptionDispatchInfo 重新抛出,完全保留堆栈
ExceptionDispatchInfo.Capture(ex).Throw();
Python 的异常链:
try:
# ...
except Exception as e:
raise RuntimeError("处理失败") from e # 保留原始异常
# 或者直接 raise,Python 自动链式异常
try:
# ...
except Exception as e:
raise # Python 3 自动添加 __context__
Python 3 的 raise 会自动保留 __context__(之前的异常),from e 会设置 __cause__(明确的因果关系)。
迁移指南:C# 开发者最容易犯的错
-
忘记
as关键字 :Python 的except Exception as e不是except Exception e -
用错异常类型 :Python 的
IndexError不是IndexOutOfRangeException -
没有
checked异常:Python 不会在编译时检查你是否捕获了所有异常 -
**滥用
except Exception**:和 C# 的catch (Exception)一样,太宽泛了 -
**忽略
else**:Python 的try-except-else的else在没有异常时执行,C# 没有这个
坑点提醒
bare except ------捕获一切异常,包括 KeyboardInterrupt:
try:
# ...
except: # 不要这样写!
pass
这会捕获 SystemExit、KeyboardInterrupt,导致你连 Ctrl+C 都用不了。永远指定异常类型。
异常吞噬 ------except 里什么都不做:
try:
# ...
except Exception:
pass # 异常被吞掉了,出 bug 都不知道
至少要 print 或 log 一下异常信息。
资源泄漏 ------不用 with:
f = open("file.txt")
content = f.read()
# 忘记 f.close(),文件句柄泄漏
永远用 with 语句管理资源。
真实案例 :我有个同事从 C# 转 Python,写了一个数据处理脚本。脚本里用 try-except Exception: pass 吞掉了所有异常,跑了三天才发现数据处理全错了------异常被吞掉,错误数据静默写入数据库。后来改成 except Exception as e: logging.error(e) 才发现问题。
try-except-else-finally:完整流程
Python 的异常处理有四个部分,比 C# 多一个 else:
try:
result = 10 / int(input("输入一个数: "))
except ZeroDivisionError:
print("不能除以零")
except ValueError:
print("请输入有效数字")
else:
# 没有异常时执行
print(f"结果是: {result}")
finally:
# 无论如何都执行
print("计算完成")
C# 没有 else,你得把正常逻辑放在 try 块里:
try
{
int num = int.Parse(Console.ReadLine());
int result = 10 / num;
Console.WriteLine($"结果是: {result}"); // 这里如果出异常也会被捕获
}
catch (DivideByZeroException)
{
Console.WriteLine("不能除以零");
}
catch (FormatException)
{
Console.WriteLine("请输入有效数字");
}
finally
{
Console.WriteLine("计算完成");
}
Python 的 else 让"正常逻辑"和"异常处理"分离得更清楚------else 里的代码不会被 try 块的异常影响,只有 else 自己抛出的异常才会被 except 捕获。
Python 3.14 新特性
PEP 758:不带圆括号的 except 和 except* 表达式
Python 3.14 允许在捕获多个异常且不使用 as 子句时省略圆括号:
# Python 3.13 及之前
try:
connect_to_server()
except (TimeoutError, ConnectionRefusedError): # 必须有圆括号
print('网络连接失败')
# Python 3.14
try:
connect_to_server()
except TimeoutError, ConnectionRefusedError: # 可以省略圆括号
print('网络连接失败')
注意 :这个简化只适用于不使用 as 子句的情况:
# 还是需要圆括号
try:
connect_to_server()
except (TimeoutError, ConnectionRefusedError) as e: # 有 as 必须用圆括号
print(f'网络连接失败: {e}')
对比 C# 的异常捕获
| 特性 | C# | Python 3.14 |
|---|---|---|
| 多类型捕获 | `catch (AException | BException ex)` |
| 圆括号 | 不需要 | 省略圆括号(无as时) |
| 异常过滤器 | when (condition) |
if 在 except 块内判断 |
| 重新抛出 | throw; |
raise |
改进的错误消息
Python 3.14 还改进了错误消息,帮助开发者更快定位问题:
# 关键字拼写错误
whille True: # 语法错误,建议使用 'while'
pass
# else 后的 elif
if x > 0:
print("正数")
else:
print("非正数")
elif x < 0: # 语法错误:else 块后不能有 elif
print("负数")
一句话总结
C# 的异常处理像写法律条文------每种情况都有明确的条款;Python 的异常处理像贴便利贴------简单直接,出问题了再处理。
下一篇咱们来聊聊自定义异常------C# 的 CustomException : Exception vs Python 的自定义异常类,两种语言的"异常哲学"又有啥不同。
📦 示例代码:C# 转 Python 全系列配套练习代码(含 48 章示例)
💬 欢迎点赞、收藏、转发,你的支持是我持续创作的动力!