查看: 165|回复: 0

Python异常处理else和finally清理资源实战

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
在 Python 中,try 异常处理结构并不只有 try 和 except,完整形式包括 try、except、else、finally 四个子句。多数脚本只使用前两个,但 else 能把成功路径从 try 中分离出来,finally 能保证资源清理一定执行。下面按执行顺序、代码实现和典型场景说明。

一、完整结构与执行顺序
  1. try:
  2.     result = perform_operation()
  3. except SomeError:
  4.     handle_error()
  5. else:
  6.     process_result(result)
  7. finally:
  8.     cleanup()
复制代码
try 总是先执行;except 只在 try 抛出匹配异常时执行;else 只在 try 无异常时执行;finally 无论是否异常、异常是否被捕获都执行。这个顺序决定了 else 和 finally 的用途。

二、else:把成功路径放到 try 外
else 的代码不在 try 保护范围内,因此它抛出的异常不会被前面的 except 捕获。推荐把可能出错的 open 和正常处理逻辑 upper 分开,避免处理逻辑 bug 被 FileNotFoundError 错误捕获。
  1. def load_and_process(filepath):
  2.     try:
  3.         data = open(filepath, 'r').read()
  4.     except FileNotFoundError:
  5.         print(f'文件不存在: {filepath}')
  6.         return None
  7.     else:
  8.         print('文件读取成功,开始处理...')
  9.         processed = data.upper()
  10.         return processed
复制代码
对比把处理逻辑一并放入 try 的写法:
  1. def load_and_process_bad(filepath):
  2.     try:
  3.         data = open(filepath, 'r').read()
  4.         processed = data.upper()
  5.         return processed
  6.     except FileNotFoundError:
  7.         print(f'文件不存在: {filepath}')
  8.         return None
复制代码
这种方式会把 upper 等处理逻辑的异常也归入 FileNotFoundError 的处理语境,不利于定位问题。

典型场景一:数据库查询
  1. def query_database(connection, query):
  2.     try:
  3.         cursor = connection.cursor()
  4.         cursor.execute(query)
  5.     except DatabaseError as e:
  6.         print(f'查询失败: {e}')
  7.         return []
  8.     else:
  9.         results = cursor.fetchall()
  10.         return results
复制代码
典型场景二:API 调用
  1. def fetch_user_data(user_id):
  2.     try:
  3.         response = requests.get(f'/api/users/{user_id}', timeout=5)
  4.         response.raise_for_status()
  5.     except requests.RequestException as e:
  6.         print(f'请求失败: {e}')
  7.         return None
  8.     else:
  9.         return response.json()
复制代码
典型场景三:输入验证
  1. def get_positive_number(prompt):
  2.     try:
  3.         value = int(input(prompt))
  4.     except ValueError:
  5.         print('请输入有效的整数')
  6.         return None
  7.     else:
  8.         if value <= 0:
  9.             print('请输入正数')
  10.             return None
  11.         return value
复制代码

三、finally:无论如何都执行
finally 的核心规则是:无论是否发生异常、异常是否被捕获,都会执行;即使 try 或 except 中有 return,finally 也会执行。注意 finally 中写 return 会覆盖之前的 return,不建议这样用。
  1. def test_finally(scenario):
  2.     try:
  3.         print('try开始')
  4.         if scenario == 'error':
  5.             raise ValueError('测试异常')
  6.         if scenario == 'success':
  7.             print('try正常完成')
  8.             return f'结果: {scenario}'
  9.     except ValueError:
  10.         print('except捕获了异常')
  11.         return '异常结果'
  12.     finally:
  13.         print('finally执行了!')
  14. print('场景1: 正常')
  15. result1 = test_finally('success')
  16. print(f'返回值: {result1}')
  17. print('场景2: 异常')
  18. result2 = test_finally('error')
  19. print(f'返回值: {result2}')
复制代码
  1. def bad_finally():
  2.     try:
  3.         return 'try中的return'
  4.     finally:
  5.         return 'finally中的return'
  6. print(bad_finally())
复制代码
bad_finally 会输出 finally中的return,说明 finally 的 return 覆盖了 try 的 return。

资源清理是 finally 最常见的用途。文件操作示例:
  1. def read_file_with_finally(filepath):
  2.     f = None
  3.     try:
  4.         f = open(filepath, 'r', encoding='utf-8')
  5.         return f.read()
  6.     except FileNotFoundError:
  7.         print(f'文件不存在: {filepath}')
  8.         return None
  9.     finally:
  10.         if f:
  11.             f.close()
  12.             print('文件已关闭')
复制代码
线程锁释放示例:
  1. import threading
  2. class ThreadSafeCounter:
  3.     def __init__(self):
  4.         self._lock = threading.Lock()
  5.         self._value = 0
  6.     def increment(self):
  7.         self._lock.acquire()
  8.         try:
  9.             self._value += 1
  10.             return self._value
  11.         finally:
  12.             self._lock.release()
复制代码
临时文件清理示例:
  1. import os
  2. def process_with_temp_file(data):
  3.     temp_path = 'temp_processing.txt'
  4.     try:
  5.         with open(temp_path, 'w') as f:
  6.             f.write(data)
  7.         return os.path.getsize(temp_path)
  8.     finally:
  9.         if os.path.exists(temp_path):
  10.             os.remove(temp_path)
  11.             print('临时文件已清理')
复制代码

四、try-except-else-finally 组合实战
数据库事务是典型组合场景:try 中执行多条 SQL,异常时 rollback,成功时在 else 中 commit,finally 关闭游标。
  1. def transfer_money(conn, from_id, to_id, amount):
  2.     try:
  3.         cursor = conn.cursor()
  4.         cursor.execute('BEGIN TRANSACTION')
  5.         cursor.execute(
  6.             'UPDATE accounts SET balance = balance - ? WHERE id = ?',
  7.             (amount, from_id)
  8.         )
  9.         cursor.execute(
  10.             'UPDATE accounts SET balance = balance + ? WHERE id = ?',
  11.             (amount, to_id)
  12.         )
  13.         cursor.execute('SELECT balance FROM accounts WHERE id = ?', (from_id,))
  14.         if cursor.fetchone()[0] < 0:
  15.             raise ValueError('余额不足')
  16.     except ValueError as e:
  17.         print(f'业务错误: {e}')
  18.         conn.rollback()
  19.         return False
  20.     except Exception as e:
  21.         print(f'数据库错误: {e}')
  22.         conn.rollback()
  23.         return False
  24.     else:
  25.         conn.commit()
  26.         print('转账成功')
  27.         return True
  28.     finally:
  29.         cursor.close()
复制代码
这里 else 只在所有 SQL 执行且余额检查通过后提交事务;任何异常进入 except 回滚;finally 关闭游标。

计时和日志装饰器也能体现完整结构:except 中记录失败耗时并重新抛出,else 中记录成功耗时并返回结果,finally 可用于统一收尾。
  1. import time
  2. import logging
  3. def timed_operation(operation_name):
  4.     def decorator(func):
  5.         def wrapper(*args, **kwargs):
  6.             start = time.perf_counter()
  7.             try:
  8.                 result = func(*args, **kwargs)
  9.             except Exception as e:
  10.                 elapsed = time.perf_counter() - start
  11.                 logging.error(
  12.                     f'{operation_name} 失败 '
  13.                     f'(耗时{elapsed:.3f}秒): {e}'
  14.                 )
  15.                 raise
  16.             else:
  17.                 elapsed = time.perf_counter() - start
  18.                 logging.info(
  19.                     f'{operation_name} 成功 '
  20.                     f'(耗时{elapsed:.3f}秒)'
  21.                 )
  22.                 return result
  23.             finally:
  24.                 pass
  25.         return wrapper
  26.     return decorator
  27. @timed_operation('数据处理')
  28. def process_large_dataset(data):
  29.     time.sleep(0.5)
  30.     return len(data)
  31. result = process_large_dataset([1, 2, 3])
复制代码

五、总结与选型
在 Python 异常处理中,try-except-else-finally 是完整工具。else 让成功路径清晰,finally 让资源清理可靠。各子句作用可归纳为:try 总是首先执行,放可能出错的代码;except 在 try 中发生异常时执行,处理已知异常;else 在 try 中没有异常时执行,处理成功结果;finally 无论如何都执行,清理资源,如关闭文件、释放锁等。
最佳实践是:with 语句可以替代大多数 finally 场景,例如文件、锁等;只有在 with 不够用时才手写 finally。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-8 13:20 , Processed in 0.036838 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部