Python 错误和异常处理

Python 有两种错误:语法错误异常

一、语法错误

语法错误是代码写法不符合 Python 语法规范,解析器在运行前就能发现:

while True print('Hello world')
# SyntaxError: invalid syntax — 缺少冒号

语法错误必须修复才能运行程序,不能通过 try/except 捕获。

二、异常

语法正确但运行时发生的错误叫做异常。即使程序没有语法错误,运行时也可能出错:

10 * (1/0)      # ZeroDivisionError: division by zero  — 除以零
4 + spam*3      # NameError: name 'spam' is not defined — 变量未定义
'2' + 2         # TypeError: can only concatenate str (not "int") to str — 类型不匹配

异常信息包含异常类型和调用栈,帮助定位问题。

三、异常处理 try/except

try/except 包裹可能出错的代码,出错时不会崩溃,而是执行 except 中的处理逻辑:

while True:
    try:
        x = int(input("请输入一个数字: "))
        break
    except ValueError:
        print("您输入的不是数字,请再次尝试输入!")

工作流程:

  1. 执行 try 子句中的代码
  2. 没有异常 → 跳过 except,继续执行后面的代码
  3. 有异常 → try 中余下的代码被忽略,匹配到对应的 except 就执行它
  4. 没有匹配的 except → 异常向上传递,最终程序报错

捕获多种异常

一个 try 可以对应多个 except,分别处理不同类型的异常:

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except OSError as err:
    print("OS error:", err)
except ValueError:
    print("Could not convert data to an integer.")
except:
    print("Unexpected error:", sys.exc_info()[0])

也可以把多个异常合并到一个 except 中处理:

try:
    x = int(input("输入数字: "))
except (ValueError, EOFError):
    print("输入有误")

最后一个 except 不带异常名称可以捕获所有异常,但不推荐滥用,容易掩盖 bug。

四、try/except...else

else 子句在 try 没有发生异常时执行,放在所有 except 之后:

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except IOError:
        print('cannot open', arg)
    else:
        print(arg, 'has', len(f.readlines()), 'lines')
        f.close()

else 比把代码全放在 try 里好,因为 else 中的代码如果出错,不会被 except 误捕获。

五、try-finally

finally 子句无论是否发生异常都会执行,常用于资源清理:

try:
    x = int(input("输入数字: "))
except ValueError:
    print("输入有误")
finally:
    print("这句话无论异常是否发生都会执行")

完整组合:try/except/else/finally

def divide(x, y):
    try:
        result = x / y
    except ZeroDivisionError:
        print("division by zero!")
    else:
        print("result is", result)
    finally:
        print("executing finally clause")

divide(2, 1)    # result is 2.0 → executing finally clause
divide(2, 0)    # division by zero! → executing finally clause
divide("2", "1")  # executing finally clause → 然后抛出 TypeError

如果异常没有被 except 捕获,finally 执行完后异常会继续向外抛出。

六、抛出异常 raise

raise 手动抛出一个异常:

x = 10
if x > 5:
    raise Exception('x 不能大于 5。x 的值为: {}'.format(x))
# Exception: x 不能大于 5。x 的值为: 10

在 except 中可以用 raise 重新抛出当前异常:

try:
    raise NameError('HiThere')
except NameError:
    print('An exception flew by!')
    raise  # 重新抛出,程序终止
# An exception flew by!
# 然后报 NameError: HiThere

七、用户自定义异常

创建自己的异常类,继承 Exception 即可:

class MyError(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)

try:
    raise MyError(2 * 2)
except MyError as e:
    print('My exception occurred, value:', e.value)
# My exception occurred, value: 4

更实际的用法——为模块建立异常体系:

class Error(Exception):
    """Base class for exceptions in this module."""
    pass

class InputError(Error):
    """输入错误"""
    def __init__(self, expression, message):
        self.expression = expression
        self.message = message

class TransitionError(Error):
    """状态转换错误"""
    def __init__(self, previous, next, message):
        self.previous = previous
        self.next = next
        self.message = message

大多数异常的名字都以 Error 结尾。

八、常用内置异常

BaseException
├── SystemExit              — sys.exit() 退出
├── KeyboardInterrupt       — Ctrl+C 中断
├── GeneratorExit           — 生成器关闭
└── Exception               — 所有用户级异常的基类
    ├── ArithmeticError     — 算术错误
    │   ├── ZeroDivisionError    — 除以零
    │   ├── OverflowError        — 溢出
    │   └── FloatingPointError   — 浮点错误
    ├── AttributeError          — 属性不存在
    ├── ImportError              — 导入失败
    │   └── ModuleNotFoundError  — 模块不存在
    ├── LookupError             — 查找失败
    │   ├── IndexError           — 索引越界
    │   └── KeyError             — 字典键不存在
    ├── NameError               — 变量未定义
    │   └── UnboundLocalError    — 局部变量未绑定
    ├── OSError                 — 系统错误
    │   ├── FileNotFoundError    — 文件不存在
    │   ├── FileExistsError      — 文件已存在
    │   ├── PermissionError      — 权限不足
    │   ├── TimeoutError         — 超时
    │   └── IsADirectoryError    — 是目录不是文件
    ├── TypeError               — 类型错误
    ├── ValueError              — 值错误
    │   └── UnicodeError         — Unicode 错误
    ├── RuntimeError            — 运行时错误
    │   ├── NotImplementedError  — 未实现
    │   └── RecursionError       — 递归深度超限
    ├── StopIteration           — 迭代器耗尽
    └── AssertionError          — 断言失败

最常用的:ValueErrorTypeErrorKeyErrorIndexErrorFileNotFoundErrorAttributeError

九、with 语句预定义清理

with 语句可以保证资源(如文件、网络连接)在使用完后自动关闭:

# 不推荐 — 文件可能不会被关闭
f = open("myfile.txt")
for line in f:
    print(line, end="")
f.close()

# 推荐 — 无论是否出错都会自动关闭
with open("myfile.txt") as f:
    for line in f:
        print(line, end="")
# 出了 with 代码块,文件自动关闭

十、assert 断言

assert 用于调试阶段检查条件是否为 True,为 False 时触发 AssertionError

x = 5
assert x > 0, "x 必须大于 0"       # ✅ 通过

# assert x < 0, "x 必须小于 0"      # ❌ AssertionError: x 必须小于 0

断言可以在解释器启动时用 -O 参数禁用,不要用 assert 做业务逻辑校验。