3、else与finally子句:else的使用场景、finally的清理作用、资源释放的最佳实践

3.1 else子句:不是你想的那样

很多人学Python异常处理时,看到else就懵了。

「else不是跟if一起用的吗?」

没错。但在try-except里,else也有自己的舞台。它的逻辑很简单:当try块没有抛出任何异常时,执行else块

核心理解:else只会在try成功执行后运行。一旦有异常被捕获,else直接跳过。

我刚开始用的时候也觉得多余。直接把代码写在try里不就行了?

后来踩了坑才明白——else能帮你精确控制异常范围。你想想看,try里放的应该是可能出错的代码。但有些代码你希望它只在try成功后才执行,又不想被try的异常捕获机制误伤。这时候else就派上用场了。

def read_config(file_path):
    try:
        with open(file_path, 'r') as f:
            config_data = f.read()
    except FileNotFoundError:
        print(f"配置文件 {file_path} 不存在")
        return None
    except PermissionError:
        print(f"没有权限读取 {file_path}")
        return None
    else:
        # 只有文件读取成功才执行解析
        print("文件读取成功,开始解析配置...")
        return parse_config(config_data)

看到没?parse_config放在else里。它只会在文件读取成功后执行。如果文件读取失败,else根本不会触发。这样你的异常处理逻辑就干净多了。

我的习惯:把「成功后的业务逻辑」放在else里。比如数据库查询成功后处理结果、API调用成功后解析响应。这样try块只负责「可能失败的操作」,else负责「成功后的操作」,职责分明。

3.2 finally子句:无论如何都要做的事

finally就更好理解了——不管try里发生了什么,finally一定会执行

哪怕try里写了return,哪怕except里又抛了异常,哪怕程序要崩溃了……finally都会在最后关头站出来,把该收的尾收掉。

为什么会这样?因为finally的设计初衷就是资源清理

def process_data():
    try:
        db = connect_to_database()
        data = db.query("SELECT * FROM users")
        return transform(data)
    except DatabaseError as e:
        print(f"数据库查询失败: {e}")
        return None
    finally:
        # 不管成功还是失败,数据库连接必须关
        db.close()
        print("数据库连接已关闭")

嗯,这里要注意:finally里的代码是在return之前执行的。上面这个例子,try里的return语句已经准备好了要返回的值,但finally会先执行完,然后才真正返回。

我曾经踩过的坑:在finally里写了return语句。结果try里的返回值被覆盖了。因为finally的return优先级更高。所以记住——不要在finally里写return,除非你故意想覆盖返回值(但99%的情况你不想)。

3.3 资源释放的最佳实践

说到资源释放,我见过太多血泪史了。

文件句柄没关、数据库连接没释放、网络socket一直挂着……这些在开发环境可能看不出问题,一上生产就炸了。

我个人习惯用上下文管理器(with语句)来处理资源。它本质上就是try-finally的语法糖,但写起来更优雅。

资源类型 传统try-finally写法 推荐with写法
文件操作 try: f = open(...) ... finally: f.close() with open(...) as f: ...
数据库连接 try: conn = db.connect() ... finally: conn.close() with db.connect() as conn: ...
线程锁 try: lock.acquire() ... finally: lock.release() with lock: ...

但有些场景下,你没法用with。比如资源需要在多个函数间传递,或者生命周期比较复杂。这时候try-finally就是你的底牌。

class ResourceManager:
    def __init__(self):
        self.resources = []
    
    def acquire_resource(self, resource):
        self.resources.append(resource)
        print(f"获取资源: {resource}")
    
    def release_all(self):
        for r in reversed(self.resources):
            try:
                r.close()
                print(f"释放资源: {r}")
            except Exception as e:
                print(f"释放资源 {r} 时出错: {e}")
        self.resources.clear()

# 使用示例
manager = ResourceManager()
try:
    res1 = open("file1.txt")
    manager.acquire_resource(res1)
    res2 = open("file2.txt")
    manager.acquire_resource(res2)
    # 业务逻辑...
finally:
    manager.release_all()

最佳实践总结

  • 能用with就用with,代码简洁又安全
  • 必须手动管理资源时,用try-finally保证释放
  • 释放资源时也要加异常处理,别让清理代码本身抛异常
  • 释放顺序要逆序(后获取的先释放),避免依赖问题

3.4 三者组合:完整的异常处理模式

最后,我把try、except、else、finally串起来,给你看一个完整的例子。

def safe_file_processor(file_path):
    """
    安全处理文件的完整模式
    """
    file = None
    try:
        # 可能出错的代码
        file = open(file_path, 'r')
        content = file.read()
        result = process_content(content)
    except FileNotFoundError:
        print(f"文件不存在: {file_path}")
        return None
    except PermissionError:
        print(f"无权限读取: {file_path}")
        return None
    except Exception as e:
        print(f"未知错误: {e}")
        return None
    else:
        # 成功后的业务逻辑
        print("文件处理成功")
        return result
    finally:
        # 无论如何都要释放资源
        if file:
            file.close()
            print("文件句柄已关闭")

这个模式我用了很多年。它把异常处理分成了四个清晰的阶段:

  1. try:只放可能出错的代码
  2. except:分类处理各种异常
  3. else:成功后的后续操作
  4. finally:无论如何都要做的清理工作

说白了,这就是一个「尝试-处理-成功-清理」的完整流程。每个阶段各司其职,代码可读性高,也不容易漏掉资源释放。

记住:异常处理不是为了处理异常而处理。它的终极目标是——让你的程序在出错时也能优雅地退出,不留下任何烂摊子

嗯,这就是else和finally的全部内容了。下一章我们聊聊自定义异常,到时候我会分享一个我亲手设计的异常体系,保证让你眼前一亮。