在 Python 中,try 异常处理结构并不只有 try 和 except,完整形式包括 try、except、else、finally 四个子句。多数脚本只使用前两个,但 else 能把成功路径从 try 中分离出来,finally 能保证资源清理一定执行。下面按执行顺序、代码实现和典型场景说明。
一、完整结构与执行顺序- try:
- result = perform_operation()
- except SomeError:
- handle_error()
- else:
- process_result(result)
- finally:
- cleanup()
复制代码 try 总是先执行;except 只在 try 抛出匹配异常时执行;else 只在 try 无异常时执行;finally 无论是否异常、异常是否被捕获都执行。这个顺序决定了 else 和 finally 的用途。
二、else:把成功路径放到 try 外
else 的代码不在 try 保护范围内,因此它抛出的异常不会被前面的 except 捕获。推荐把可能出错的 open 和正常处理逻辑 upper 分开,避免处理逻辑 bug 被 FileNotFoundError 错误捕获。- def load_and_process(filepath):
- try:
- data = open(filepath, 'r').read()
- except FileNotFoundError:
- print(f'文件不存在: {filepath}')
- return None
- else:
- print('文件读取成功,开始处理...')
- processed = data.upper()
- return processed
复制代码 对比把处理逻辑一并放入 try 的写法:- def load_and_process_bad(filepath):
- try:
- data = open(filepath, 'r').read()
- processed = data.upper()
- return processed
- except FileNotFoundError:
- print(f'文件不存在: {filepath}')
- return None
复制代码 这种方式会把 upper 等处理逻辑的异常也归入 FileNotFoundError 的处理语境,不利于定位问题。
典型场景一:数据库查询- def query_database(connection, query):
- try:
- cursor = connection.cursor()
- cursor.execute(query)
- except DatabaseError as e:
- print(f'查询失败: {e}')
- return []
- else:
- results = cursor.fetchall()
- return results
复制代码 典型场景二:API 调用- def fetch_user_data(user_id):
- try:
- response = requests.get(f'/api/users/{user_id}', timeout=5)
- response.raise_for_status()
- except requests.RequestException as e:
- print(f'请求失败: {e}')
- return None
- else:
- return response.json()
复制代码 典型场景三:输入验证- def get_positive_number(prompt):
- try:
- value = int(input(prompt))
- except ValueError:
- print('请输入有效的整数')
- return None
- else:
- if value <= 0:
- print('请输入正数')
- return None
- return value
复制代码
三、finally:无论如何都执行
finally 的核心规则是:无论是否发生异常、异常是否被捕获,都会执行;即使 try 或 except 中有 return,finally 也会执行。注意 finally 中写 return 会覆盖之前的 return,不建议这样用。- def test_finally(scenario):
- try:
- print('try开始')
- if scenario == 'error':
- raise ValueError('测试异常')
- if scenario == 'success':
- print('try正常完成')
- return f'结果: {scenario}'
- except ValueError:
- print('except捕获了异常')
- return '异常结果'
- finally:
- print('finally执行了!')
- print('场景1: 正常')
- result1 = test_finally('success')
- print(f'返回值: {result1}')
- print('场景2: 异常')
- result2 = test_finally('error')
- print(f'返回值: {result2}')
复制代码- def bad_finally():
- try:
- return 'try中的return'
- finally:
- return 'finally中的return'
- print(bad_finally())
复制代码 bad_finally 会输出 finally中的return,说明 finally 的 return 覆盖了 try 的 return。
资源清理是 finally 最常见的用途。文件操作示例:- def read_file_with_finally(filepath):
- f = None
- try:
- f = open(filepath, 'r', encoding='utf-8')
- return f.read()
- except FileNotFoundError:
- print(f'文件不存在: {filepath}')
- return None
- finally:
- if f:
- f.close()
- print('文件已关闭')
复制代码 线程锁释放示例:- import threading
- class ThreadSafeCounter:
- def __init__(self):
- self._lock = threading.Lock()
- self._value = 0
- def increment(self):
- self._lock.acquire()
- try:
- self._value += 1
- return self._value
- finally:
- self._lock.release()
复制代码 临时文件清理示例:- import os
- def process_with_temp_file(data):
- temp_path = 'temp_processing.txt'
- try:
- with open(temp_path, 'w') as f:
- f.write(data)
- return os.path.getsize(temp_path)
- finally:
- if os.path.exists(temp_path):
- os.remove(temp_path)
- print('临时文件已清理')
复制代码
四、try-except-else-finally 组合实战
数据库事务是典型组合场景:try 中执行多条 SQL,异常时 rollback,成功时在 else 中 commit,finally 关闭游标。- def transfer_money(conn, from_id, to_id, amount):
- try:
- cursor = conn.cursor()
- cursor.execute('BEGIN TRANSACTION')
- cursor.execute(
- 'UPDATE accounts SET balance = balance - ? WHERE id = ?',
- (amount, from_id)
- )
- cursor.execute(
- 'UPDATE accounts SET balance = balance + ? WHERE id = ?',
- (amount, to_id)
- )
- cursor.execute('SELECT balance FROM accounts WHERE id = ?', (from_id,))
- if cursor.fetchone()[0] < 0:
- raise ValueError('余额不足')
- except ValueError as e:
- print(f'业务错误: {e}')
- conn.rollback()
- return False
- except Exception as e:
- print(f'数据库错误: {e}')
- conn.rollback()
- return False
- else:
- conn.commit()
- print('转账成功')
- return True
- finally:
- cursor.close()
复制代码 这里 else 只在所有 SQL 执行且余额检查通过后提交事务;任何异常进入 except 回滚;finally 关闭游标。
计时和日志装饰器也能体现完整结构:except 中记录失败耗时并重新抛出,else 中记录成功耗时并返回结果,finally 可用于统一收尾。- import time
- import logging
- def timed_operation(operation_name):
- def decorator(func):
- def wrapper(*args, **kwargs):
- start = time.perf_counter()
- try:
- result = func(*args, **kwargs)
- except Exception as e:
- elapsed = time.perf_counter() - start
- logging.error(
- f'{operation_name} 失败 '
- f'(耗时{elapsed:.3f}秒): {e}'
- )
- raise
- else:
- elapsed = time.perf_counter() - start
- logging.info(
- f'{operation_name} 成功 '
- f'(耗时{elapsed:.3f}秒)'
- )
- return result
- finally:
- pass
- return wrapper
- return decorator
- @timed_operation('数据处理')
- def process_large_dataset(data):
- time.sleep(0.5)
- return len(data)
- result = process_large_dataset([1, 2, 3])
复制代码
五、总结与选型
在 Python 异常处理中,try-except-else-finally 是完整工具。else 让成功路径清晰,finally 让资源清理可靠。各子句作用可归纳为:try 总是首先执行,放可能出错的代码;except 在 try 中发生异常时执行,处理已知异常;else 在 try 中没有异常时执行,处理成功结果;finally 无论如何都执行,清理资源,如关闭文件、释放锁等。
最佳实践是:with 语句可以替代大多数 finally 场景,例如文件、锁等;只有在 with 不够用时才手写 finally。 |