飞翔飞翔
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
  • 学习路径
  • 第1章 Python简介

    • Python是什么
    • 安装与运行
    • 交互式解释器
    • 注释与编码规范
  • 第2章 变量与数据类型

    • 变量与对象
    • 整数 int
    • 浮点数 float
    • 复数 complex
    • 布尔值 bool
    • 字符串 str
    • 空值 None
    • 类型转换
  • 第3章 运算符与表达式

    • 算术运算符
    • 比较运算符
    • 赋值运算符
    • 逻辑运算符
    • 位运算符
    • 身份与成员运算符
    • 海象运算符
    • 运算符优先级
  • 第4章 流程控制

    • if 语句
    • if-else 语句
    • if-elif-else 语句
    • match-case 语句
    • 条件表达式(三元运算符)
    • while 循环
    • for 循环
    • range 函数
    • break 与 continue
    • 循环的 else 子句
    • pass 语句
  • 第5章 数据结构

    • 列表创建与索引
    • 列表方法
    • 列表推导式
    • 元组
    • 序列解包
    • 集合
    • 字典创建与访问
    • 字典方法
    • 字典推导式
    • range 对象
  • 第6章 函数

    • 定义函数
    • 位置参数与关键字参数
    • 默认参数
    • 可变参数
    • 解包实参
    • 函数返回值
    • lambda 表达式
    • 文档字符串与注解
    • 作用域与命名空间
    • global 与 nonlocal
  • 第7章 模块与包

    • 模块导入
    • 模块搜索路径
    • 包与相对导入
    • 标准库概览
  • 第8章 文件与输入输出

    • 文件读写
    • 上下文管理器
    • 字符串格式化
    • JSON 与 CSV
  • 第9章 面向对象

    • 类与对象
    • 方法
    • 实例变量与类变量
    • 私有变量
    • 继承
    • 多重继承
    • 魔术方法
    • 属性装饰器
    • 数据类 dataclass
  • 第10章 异常处理

    • 语法错误与异常
    • try-except
    • 异常链与 raise
    • 清理操作
    • 自定义异常
  • 第11章 迭代器与生成器

    • 迭代器协议
    • 生成器
    • 生成器表达式
    • 迭代工具
  • 第12章 高级特性

    • 装饰器
    • 函数式编程
  • 第13章 工程实践

    • 测试与调试
    • 代码质量
    • 虚拟环境

try-except

try 语句是 Python 异常处理的核心机制,它允许程序在可能出错的代码周围设置"防护网",并在异常发生时执行预定的恢复逻辑,而不是直接崩溃。

基本结构:try/except

try 子句包裹可能触发异常的代码。如果执行期间发生异常,Python 会跳过 try 中剩余的语句,转而寻找匹配的 except 子句。

while True:
    try:
        x = int(input("请输入一个整数:"))
        break
    except ValueError:
        print("输入无效,请重新输入...")

这个循环会持续请求输入,直到用户提供一个能被 int() 转换的有效整数。如果输入 "abc",int() 抛出 ValueError,except ValueError 捕获它并打印提示,然后循环继续。如果用户按 Ctrl+C,会触发 KeyboardInterrupt——由于它不是 ValueError 的子类,不会被这个 except 捕获,程序正常中断。

多 except 子句与捕获顺序

一个 try 可以跟随多个 except 子句,为不同异常类型指定不同的处理逻辑。但最多只有一个处理程序会被执行,即第一个匹配的类型决定处理路径。

try:
    f = open("data.txt")
    s = f.readline()
    i = int(s.strip())
    result = 100 / i
except OSError as err:
    print(f"文件操作失败:{err}")
except ValueError:
    print("文件内容不是有效整数")
except ZeroDivisionError:
    print("文件中存储的整数为零,无法做除法")

异常匹配遵循继承层次:except 子句中指定的类会匹配该类本身及其所有子类的实例,但不会匹配其父类。这意味着 except 子句的顺序至关重要——应该把更具体的子类放在前面,宽泛的父类放在后面。

class B(Exception):
    pass

class C(B):
    pass

class D(C):
    pass

for cls in [B, C, D]:
    try:
        raise cls()
    except D:
        print("D")
    except C:
        print("C")
    except B:
        print("B")
# 输出:B C D

如果颠倒顺序,把 except B 放在最前面,由于 C 和 D 都是 B 的子类,所有异常都会被第一个子句捕获,输出变成 B B B。

元组合并捕获

当多种异常需要相同的处理逻辑时,可以用圆括号将异常类型组成元组,写在一个 except 子句中:

try:
    data = json.loads(raw)
    value = data["key"]
except (json.JSONDecodeError, KeyError, TypeError) as e:
    print(f"数据解析失败:{type(e).__name__}: {e}")

这种方式让代码更紧凑,同时保持了对不同异常类型的精确捕获。注意元组内的顺序不影响匹配行为,因为它们在同一个 except 子句中。

as 绑定:获取异常实例

在 except 子句中使用 as 可以将异常实例绑定到一个变量,从而访问异常的参数、类型和自定义属性。

try:
    raise Exception("参数1", "参数2")
except Exception as inst:
    print(type(inst))       # <class 'Exception'>
    print(inst.args)        # ('参数1', '参数2')
    print(inst)             # ('参数1', '参数2')
    x, y = inst.args        # 解包参数
    print(f"x={x}, y={y}")

异常实例的 args 属性以元组保存构造时传入的所有参数。内置异常通常覆盖 __str__(),使得 print(inst) 显示人类可读的消息。对于自定义异常,可以在类中定义 __str__() 来控制输出格式。

try:
    open("/不存在的路径/文件.txt")
except OSError as e:
    print(f"错误码:{e.errno}")      # 2
    print(f"错误消息:{e.strerror}")  # No such file or directory
    print(f"文件名:{e.filename}")     # /不存在的路径/文件.txt

OSError 及其子类(如 FileNotFoundError)提供了 errno、strerror、filename 等属性,便于程序根据错误码做精细化处理。

else 子句

try 语句可以包含一个 else 子句,它必须放在所有 except 子句之后。else 中的代码只在 try 子句没有触发任何异常时执行。

for arg in sys.argv[1:]:
    try:
        f = open(arg, "r")
    except OSError:
        print(f"无法打开文件:{arg}")
    else:
        print(f"{arg} 有 {len(f.readlines())} 行")
        f.close()

使用 else 的好处是避免意外捕获非 try 子句保护的代码所触发的异常。如果把 print(...) 和 f.close() 直接放进 try,当 len(f.readlines()) 或 f.close() 触发异常时,可能会被外层的 except OSError 误捕——尽管这些操作与文件打开无关。

finally 子句

finally 子句定义了无论是否发生异常都必须执行的清理操作。它会在 try 语句结束前最后执行,即使 try 中触发了未被捕获的异常,finally 也会先执行,然后再重新抛出该异常。

try:
    raise KeyboardInterrupt
finally:
    print("清理工作已完成")
# 输出:清理工作已完成
# 然后重新抛出 KeyboardInterrupt

finally 的典型用途是释放外部资源:关闭文件、断开网络连接、释放锁等。无论操作成功还是失败,资源都必须被妥善释放。

def divide(x, y):
    try:
        result = x / y
    except ZeroDivisionError:
        print("除数为零!")
        return None
    else:
        print(f"结果是 {result}")
        return result
    finally:
        print("执行 finally 子句")

print(divide(2, 1))   # 结果正常,finally 执行
print(divide(2, 0))   # 捕获异常,finally 执行
print(divide("2", "1"))  # TypeError 未被捕获,finally 执行后重新抛出

注意一个特殊规则:如果 finally 子句中包含 return、break 或 continue,未被处理的异常将不会被重新引发。此外,如果 try 和 finally 中都有 return,最终返回值来自 finally。

def demo():
    try:
        return "try 的返回值"
    finally:
        return "finally 的返回值"

print(demo())  # finally 的返回值

Exception 通配捕获

Exception 是所有非致命异常的基类,可以用作通配符捕获绝大多数运行时异常。但好的做法是尽可能具体地声明要处理的异常类型,让意外的异常继续向上传播,便于发现真正的程序缺陷。

try:
    risky_operation()
except Exception as err:
    print(f"未预期的错误:{err=}, {type(err)=}")
    raise  # 记录后重新抛出,让上层处理

except Exception 不会捕获 SystemExit、KeyboardInterrupt 和 GeneratorExit,因为它们直接继承自 BaseException 而非 Exception。这使得按 Ctrl+C 仍能正常中断程序。最危险的写法是裸 except: 或 except BaseException:,它会吞掉所有异常包括系统退出信号,通常应当避免。

# ❌ 危险:会捕获 KeyboardInterrupt,导致 Ctrl+C 无法退出
# try:
#     long_running_task()
# except:
#     pass

# ✅ 安全:只捕获常规异常,保留中断能力
try:
    long_running_task()
except Exception as e:
    logger.error(f"任务失败:{e}")
    raise

异常在调用链中的传播

异常处理程序不仅处理 try 子句中直接发生的异常,还处理在 try 子句中调用的函数(包括间接调用)内部发生的异常。

def this_fails():
    x = 1 / 0

try:
    this_fails()
except ZeroDivisionError as err:
    print(f"处理运行时错误:{err}")

this_fails() 内部触发的 ZeroDivisionError 会沿着调用栈向上传播,直到被 try/except 捕获。如果没有任何处理程序匹配,它就是未处理异常,程序终止并打印完整的 Traceback。

上一页
语法错误与异常
下一页
异常链与 raise