错误和异常
程序运行过程中难免会遇到各种错误。Python 把错误分成两大类:语法错误和异常。理解它们的区别,并学会用异常处理机制写出更健壮的代码,是进阶 Python 编程的重要一步。
1. 语法错误
语法错误(Syntax Error) 是代码还没开始运行就被解释器发现的错误,也叫解析错误。初学者最常遇到,通常是因为漏写了冒号、括号不匹配、缩进错误等。
while True print('Hello world')运行后会输出类似下面的信息:
File "<stdin>", line 1
while True print('Hello world')
^
SyntaxError: invalid syntax解释器会指出出错的大致位置,并用 ^ 标记最先发现错误的地方。上面的例子中,print() 前面少了一个冒号 :,导致 while 语句语法不完整。
2. 异常
即使语法完全正确,程序运行期间也可能发生错误,这种运行期检测到的错误被称为异常(Exception)。
# 0 不能作为除数
10 * (1 / 0)
# 使用了未定义的变量
4 + spam * 3
# 字符串和整数不能直接相加
'2' + 2对应报错:
ZeroDivisionError: division by zero
NameError: name 'spam' is not defined
TypeError: can only concatenate str (not "int") to str异常信息包含两部分:
- 异常类型:如
ZeroDivisionError、NameError、TypeError。 - 错误描述:说明具体出错原因。
3. 异常处理
3.1 try/except 基本用法
使用 try/except 语句可以捕获异常,防止程序因意外错误而直接崩溃。
while True:
try:
x = int(input('请输入一个数字: '))
break
except ValueError:
print('您输入的不是数字,请再次尝试输入!')执行逻辑:
- 先执行
try块中的代码。 - 如果没有异常发生,跳过
except块。 - 如果发生异常,且异常类型与
except后面的名称匹配,执行对应的except块。 - 如果没有匹配的
except块,异常会继续向上层抛出。
异常处理不仅能捕获
try子句中直接发生的异常,还能捕获try子句中调用的函数(甚至间接调用的函数)里抛出的异常。
def this_fails():
x = 1 / 0
try:
this_fails()
except ZeroDivisionError as err:
print(f'处理运行期错误: {err}')3.2 捕获多个异常
一个 try 可以跟多个 except,分别处理不同类型的异常。也可以把多种异常写成一个元组。
import sys
try:
f = open('myfile.txt')
s = f.readline()
i = int(s.strip())
except OSError as err:
print(f'OS error: {err}')
except ValueError:
print('无法把数据转换成整数。')
except:
print(f'未知错误: {sys.exc_info()[0]}')
raise# 同时捕获多种异常
except (RuntimeError, TypeError, NameError):
pass最后一个不带异常类型的 except 相当于通配符,但尽量少用,避免隐藏真正的 bug。
4. try/except...else
try 还可以带一个 else 子句,只有当 try 块没有抛出异常时才会执行。else 必须放在所有 except 之后。
for arg in sys.argv[1:]:
try:
f = open(arg, 'r')
except IOError:
print(f'无法打开 {arg}')
else:
print(f'{arg} 有 {len(f.readlines())} 行')
f.close()把「没有异常时才执行」的代码放到 else 中,比全部塞进 try 块更清晰,也能避免捕获到意料之外的异常。
5. try-finally
finally 子句中的代码无论是否发生异常都会执行,常用于释放资源、关闭文件、断开连接等清理操作。
try/except/else/finally 四个子句的作用可以概括如下:
| 子句 | 执行时机 |
|---|---|
try | 执行可能出错的代码 |
except | 发生异常时执行的代码 |
else | 没有异常时执行的代码 |
finally | 不管有没有异常都会执行的代码 |
try:
raise KeyboardInterrupt
finally:
print('Goodbye, world!')输出:
Goodbye, world!
KeyboardInterrupt即使在 try 中发生了未被捕获的异常,finally 也会先执行完,再把异常抛出。
def divide(x, y):
try:
result = x / y
except ZeroDivisionError:
print('除数不能为 0')
else:
print(f'结果是 {result}')
finally:
print('执行 finally 子句')
divide(2, 1) # 正常情况
divide(2, 0) # 捕获异常
divide('2', '1') # 未捕获的 TypeError,finally 执行后仍会抛出同一个 try 语句里也可以同时包含 except、else 和 finally:
try:
do_something()
except AssertionError as error:
print(error)
else:
try:
with open('file.log') as file:
read_data = file.read()
except FileNotFoundError as fnf_error:
print(fnf_error)
finally:
print('这句话,无论异常是否发生都会执行。')6. 抛出异常
使用 raise 语句可以主动抛出异常。
x = 10
if x > 5:
raise Exception(f'x 不能大于 5。x 的值为: {x}')输出:
Exception: x 不能大于 5。x 的值为: 10raise 的参数可以是异常类,也可以是异常实例。只想把捕获到的异常继续向上抛时,可以直接用 raise:
try:
raise NameError('HiThere')
except NameError:
print('一个异常飞过去了!')
raise7. 用户自定义异常
通过继承 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(f'我的异常发生了,value: {e.value}')如果一个模块可能抛出多种相关异常,通常会先定义一个基础异常类,再派生具体的异常子类:
class Error(Exception):
"""本模块的异常基类。"""
pass
class InputError(Error):
"""输入错误。"""
def __init__(self, expression, message):
self.expression = expression
self.message = message
class TransitionError(Error):
"""状态转换错误。"""
def __init__(self, previous, next_state, message):
self.previous = previous
self.next_state = next_state
self.message = message按照惯例,自定义异常类名通常以 Error 结尾。
8. 预定义的清理行为
一些对象自带标准的清理行为,最常见的就是文件对象。使用 with 语句可以确保对象使用完毕后自动调用清理方法,即使中间发生异常也不例外。
# 不推荐:发生异常时文件可能没关闭
for line in open('myfile.txt'):
print(line, end='')# 推荐:with 语句自动关闭文件
with open('myfile.txt') as f:
for line in f:
print(line, end='')with 语句本质上是上下文管理器,它让资源管理更简洁、更安全。
9. 常见内置异常速查
| 异常类型 | 说明 |
|---|---|
SyntaxError | 语法错误 |
ZeroDivisionError | 除数为 0 |
NameError | 使用了未定义的变量 |
TypeError | 类型不匹配 |
ValueError | 值不合法 |
KeyError | 字典中找不到指定键 |
IndexError | 索引越界 |
AttributeError | 对象没有该属性 |
FileNotFoundError | 文件不存在 |
OSError | 操作系统错误 |
10. 综合案例
下面是一个简单的数字输入处理函数,演示了 try/except/else/finally 的组合用法:
def get_int(prompt):
try:
value = int(input(prompt))
except ValueError:
print('输入错误:必须是整数。')
return None
else:
print(f'你输入的是 {value}')
return value
finally:
print('输入处理完成')
num = get_int('请输入一个整数: ')
if num is not None:
print(f'输入有效: {num}')总结
Python 的错误分为语法错误和异常。异常处理的核心是 try/except/else/finally:
try放可能出错的代码。except捕获并处理特定异常。else在无异常时执行。finally无论是否异常都执行,适合资源清理。
需要主动报告错误时用 raise;需要自定义语义更明确的异常时,可以继承 Exception。对于文件、网络连接等资源,优先使用 with 语句,让清理行为自动发生。