卡码笔记-最强八股文
首页
计算机基础
Python
C++
Java
Go
🔥大模型🔥
  • 大模型面经
  • Java面经
  • C++面经
大模型Github (opens new window)
代码随想录 (opens new window)
首页
计算机基础
Python
C++
Java
Go
🔥大模型🔥
  • 大模型面经
  • Java面经
  • C++面经
大模型Github (opens new window)
代码随想录 (opens new window)
  • 本栏必读

    • Python面试题专栏介绍
    • Python学习路线
    • Python零基础入门课
  • 基础语法

  • 函数、作用域与模块

  • 面向对象

  • 进阶语法与语言机制

    • 可迭代对象和迭代器有什么区别?
    • 生成器是什么?yield为什么能节省内存?
    • Python装饰器的原理是什么?
    • Python上下文管理器是什么?with如何自动释放资源?
      • 简要回答
      • 详细回答
      • 知识拓展
    • Python异常体系是什么?try、except、else和finally怎么执行?
    • Python浅拷贝和深拷贝有什么区别?
    • Python类型标注有什么用?常见类型应该怎么写?
    • dataclass、namedtuple和普通类有什么区别?
    • Python如何管理内存?引用计数和垃圾回收是什么?

# Python上下文管理器是什么?with如何自动释放资源?

面试官问:"Python上下文管理器是什么?with语句如何自动释放资源?"

很多人会答"with可以自动关闭文件",但这只是应用场景。真正要讲清楚的是:上下文管理器是实现了__enter__()和__exit__()方法的对象,with语句在进入时调用__enter__(),在退出时无论是否异常都调用__exit__()释放资源。

# 简要回答

上下文管理器(Context Manager) 是实现了上下文管理协议的对象,需要定义两个方法:

  • __enter__():进入with代码块时调用,返回值赋给as后的变量。
  • __exit__(exc_type, exc_val, exc_tb):退出with代码块时调用,无论正常退出还是异常退出都会执行。

with语句的工作流程:

  1. 调用上下文管理器的__enter__()方法,返回值赋给as变量。
  2. 执行with代码块。
  3. 无论代码块是否抛出异常,都调用__exit__()方法。
  4. 如果__exit__()返回True,异常被抑制;否则异常继续传播。

为什么能自动释放资源:资源释放逻辑写在__exit__()中,Python保证无论with代码块如何退出,__exit__()都会被调用,类似其他语言的try...finally。

# 详细回答

# with语句的基本用法

最常见的场景是文件操作:

with open("data.txt") as f:
    content = f.read()
    print(content)
# 退出with块后,文件自动关闭
1
2
3
4

等价于:

f = open("data.txt")
try:
    content = f.read()
    print(content)
finally:
    f.close()
1
2
3
4
5
6

with语句简化了try...finally的写法,保证资源一定会被释放。

# 实现自定义上下文管理器

通过类实现上下文管理协议:

class DatabaseConnection:
    def __init__(self, host):
        self.host = host
        self.conn = None
    
    def __enter__(self):
        print(f"连接数据库 {self.host}")
        self.conn = f"Connection to {self.host}"
        return self.conn
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print(f"关闭数据库连接")
        self.conn = None
        return False  # 不抑制异常

with DatabaseConnection("localhost") as conn:
    print(f"使用连接: {conn}")
    print("执行查询")
# 输出:
# 连接数据库 localhost
# 使用连接: Connection to localhost
# 执行查询
# 关闭数据库连接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

__enter__()返回的值赋给conn,__exit__()在退出时自动调用。

# __exit__的异常处理

__exit__()接收三个参数,描述with代码块中的异常:

  • exc_type:异常类型。
  • exc_val:异常实例。
  • exc_tb:异常traceback。

如果没有异常,三个参数都是None。

class Resource:
    def __enter__(self):
        print("获取资源")
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is None:
            print("正常退出,释放资源")
        else:
            print(f"异常退出: {exc_type.__name__}, 释放资源")
        return False

with Resource():
    print("使用资源")
    raise ValueError("出错了")
# 输出:
# 获取资源
# 使用资源
# 异常退出: ValueError, 释放资源
# (然后抛出ValueError)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

__exit__()返回False或不返回值(默认None)时,异常继续传播。返回True时,异常被抑制。

with语句执行流程

# 抑制异常

__exit__()返回True可以吞掉异常:

class SuppressException:
    def __enter__(self):
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is ValueError:
            print("捕获ValueError,不传播")
            return True  # 抑制异常
        return False

with SuppressException():
    print("开始")
    raise ValueError("错误")
    print("这行不会执行")
print("继续执行")
# 输出:
# 开始
# 捕获ValueError,不传播
# 继续执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

这种方式不推荐,因为隐式吞掉异常会让调试困难。通常应该让异常传播,在外层统一处理。

# 使用contextlib简化实现

contextlib.contextmanager装饰器可以用生成器实现上下文管理器:

from contextlib import contextmanager

@contextmanager
def database_connection(host):
    print(f"连接数据库 {host}")
    conn = f"Connection to {host}"
    try:
        yield conn  # yield之前是__enter__,之后是__exit__
    finally:
        print("关闭数据库连接")

with database_connection("localhost") as conn:
    print(f"使用连接: {conn}")
1
2
3
4
5
6
7
8
9
10
11
12
13

yield之前的代码相当于__enter__(),yield的值赋给as变量,yield之后的代码相当于__exit__()。try...finally保证清理代码一定执行。

# 多个上下文管理器

可以在一个with语句中使用多个上下文管理器:

with open("input.txt") as f_in, open("output.txt", "w") as f_out:
    content = f_in.read()
    f_out.write(content.upper())
1
2
3

等价于嵌套with:

with open("input.txt") as f_in:
    with open("output.txt", "w") as f_out:
        content = f_in.read()
        f_out.write(content.upper())
1
2
3
4

多个上下文管理器从左到右依次进入,从右到左依次退出。

# 知识拓展

Q1: 为什么文件对象可以用with语句?

文件对象实现了上下文管理协议。open()返回的文件对象有__enter__()返回自己,__exit__()调用close()方法:

f = open("data.txt")
print(hasattr(f, "__enter__"))  # True
print(hasattr(f, "__exit__"))   # True
1
2
3

Q2: 上下文管理器的典型应用场景有哪些?

  • 文件操作:自动关闭文件。
  • 数据库连接:自动关闭连接或提交/回滚事务。
  • 锁管理:threading.Lock自动释放锁。
  • 临时修改状态:临时改变工作目录、环境变量、日志级别。
  • 性能计时:记录代码块执行时间。

Q3: 如果__enter__抛出异常,__exit__还会执行吗?

不会。只有成功进入with代码块(即__enter__()成功返回),__exit__()才会执行。__enter__()抛出异常时,上下文管理器没有被激活,不需要清理。

Q4: contextlib还有哪些有用的工具?

  • contextlib.closing:为没有实现上下文管理协议但有close()方法的对象提供with支持。
  • contextlib.suppress:简洁地抑制指定异常类型。
  • contextlib.ExitStack:动态管理多个上下文管理器。
from contextlib import suppress

with suppress(FileNotFoundError):
    os.remove("nonexistent.txt")
# 文件不存在时不报错,继续执行
1
2
3
4
5

Q5: with语句可以用于异步场景吗?

可以。Python 3.5+支持异步上下文管理器,需要实现__aenter__()和__aexit__():

class AsyncResource:
    async def __aenter__(self):
        print("异步获取资源")
        return self
    
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        print("异步释放资源")
        return False

async def main():
    async with AsyncResource():
        print("使用资源")
1
2
3
4
5
6
7
8
9
10
11
12

配合async with使用,适用于异步数据库连接、异步文件操作等场景。

Last Updated: 10/13/2026, 5:57:00 PM

← Python装饰器的原理是什么? Python异常体系是什么?try、except、else和finally怎么执行? →

评论

验证登录状态...

侧边栏 侧边栏
夜间模式 夜间
卡码简历 卡码简历
代码随想录 代码随想录
卡码投递表 卡码投递表🔥
2026实习校招群 2026群
添加客服微信 2026实习校招客服微信 PS:通过微信后,请发送姓名-学校-年级-2026实习/校招
支持卡码笔记 支持卡码笔记
鼓励/支持/赞赏Carl 卡码笔记赞赏码
1. 如果感觉本站对你很有帮助,也可以请Carl喝杯奶茶,金额大小不重要,心意已经收下
2. 希望大家都能梦想成真,有好的前程,加油💪