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("您输入的不是数字,请再次尝试输入!")
工作流程:
- 执行 try 子句中的代码
- 没有异常 → 跳过 except,继续执行后面的代码
- 有异常 → try 中余下的代码被忽略,匹配到对应的 except 就执行它
- 没有匹配的 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 — 断言失败
最常用的:ValueError、TypeError、KeyError、IndexError、FileNotFoundError、AttributeError。
九、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 做业务逻辑校验。