跳转到主要内容
模块 9 · 第 3 课(共 4)课程中的35/36~12 min
模块课程(3/4)

上下文管理器:with 和 __enter__/__exit__

上下文管理器是与 with 一起使用的对象。它保证 某些设置/清理操作总是会发生,即使发生错误也是如此。 典型示例:打开和关闭文件。

with 模式

Python
with open("note.txt", "w") as f:
    f.write("ciao")
# qui f è già chiuso, anche se write() ha sollevato un'eccezione

用简单的英语来说:“以资源 X 打开为 f,执行此操作。完成后 (无论发生什么),关闭 X。”如果没有 with 你就必须写 每次都是 try/finally 。

一次不止一个

Python
with open("a.txt") as f, open("b.txt") as g:
    dati_a = f.read()
    dati_b = g.read()

编写自己的上下文管理器(类)

您需要定义两个 dunder 方法:__enter__ 和 __exit__。

Python
class Timer:
    def __enter__(self):
        import time
        self.t0 = time.perf_counter()
        return self  # è l'oggetto che finisce dopo "as"
    def __exit__(self, exc_type, exc_value, traceback):
        import time
        self.elapsed = time.perf_counter() - self.t0
        # ritornare True ingoia l'eccezione; di solito non lo vuoi
        return False

with Timer() as t:
    sum(range(10**6))
t.elapsed  # quanto è durato
  • __enter__(self) 在进入块时被调用;返回值是 as 之后得到的值。
  • __exit__(self, exc_type, exc_value, tb) 在退出时被调用(总是)。如果它返回 True,则任何异常都会被抑制。

快捷方式:@contextmanager

contextlib.contextmanager 允许您将上下文管理器编写为 生成器函数:

Python
from contextlib import contextmanager

@contextmanager
def timer():
    import time
    t0 = time.perf_counter()
    try:
        yield  # qui sta il blocco "with"
    finally:
        elapsed = time.perf_counter() - t0
        print(f"durata: {elapsed:.4f}s")

with timer():
    sum(range(10**6))

yield 之前的代码是 __enter__,之后的代码是 __exit__。 即使发生错误,try/finally 也能保证清理。

抑制:忽略异常

Python
from contextlib import suppress

with suppress(FileNotFoundError):
    open("forse-non-esiste.txt").read()
# se il file non c'è, nessun errore propaga

陷阱:with 仅适用于支持它的对象

Python
x = 42
with x:  # AttributeError: __enter__
    ...

使用 @contextmanager 编写上下文管理器

标准库的 contextlib 模块导出一个名为 @contextmanager 的非常方便的装饰器,以使用生成器函数创建上下文管理器,而不是编写实现 __enter__ 和 __exit__ 方法的详细类。您只需编写一个使用单个 yield 的生成器函数:

Python
from contextlib import contextmanager

@contextmanager
def manage_resource():
    # __enter__ code
    yield resource
    # __exit__ code

自己尝试一下

锻炼#python.m9.l3.e1
尝试:0加载中...

使用 contextlib.suppress 尝试 int('non-numero') 忽略 ValueError。在块之后分配“ok = True”。评价“好”。

正在加载编辑器...
显示提示

抑制(ValueError)仅吞咽ValueError。

3 次尝试后可用的解决方案

复习练习

锻炼#python.m9.l3.e2
尝试:0加载中...

使用 @contextmanager 编写一个上下文管理器“collect”,它会生成一个空列表,并在退出时将其转换为元组(打印它)。使用它将 1、2、3 附加到块内的列表中。最后,评估“len(collection)”,其中“collection”是生成的列表。

正在加载编辑器...
显示提示

产生项目以公开 with 块内的列表。

3 次尝试后可用的解决方案

额外的挑战

锻炼#python.m9.l3.e3
尝试:0加载中...

Write a custom context manager using the `@contextmanager` decorator from `contextlib`. The context manager must be named `mock_session`. Upon entry, it must append `'start'` to a global list `log_list = []`, yield control using `yield`, and upon exit append `'stop'`. Finally, evaluate `log_list` after using the context manager with `with mock_session():`.

正在加载编辑器...
显示提示

在附加“stop”之前,在mock_session中使用yield来暂停执行。

3 次尝试后可用的解决方案