为什么生成器不是上下文管理器?

3 投票
2 回答
771 浏览
提问于 2025-04-18 08:27

生成器可以用来管理一些资源,比如在上下文管理器里面使用 yield。当你调用生成器的 close() 方法(或者发生异常时),这个资源就会被释放。

因为有时候我们会忘记在最后调用 close(),所以我觉得使用上下文管理器来处理这个问题是很明显的选择,这样也能处理可能出现的异常。我知道可以用 contextlib.closing 来做到这一点,但如果能直接在 with 语句中使用生成器,不是更好吗?

有没有什么理由让生成器 能成为上下文管理器呢?

2 个回答

1

就像Wheaties说的,你希望类只做“一件事,并且做到最好”。特别是对于上下文管理器,它们就是在管理一个上下文。那么,问问自己,这里的上下文是什么呢?大多数情况下,就是在打开一个资源。之前我问过关于如何使用队列和上下文管理器的问题,得到的回答基本上是队列作为上下文没有意义。然而,“在一个任务中”才是真正的上下文,这样做上下文管理器就有意义了。

另外,没有可以重复使用的with语句。举个例子,我不能在一行代码中打开一个文件并遍历它,像这样:

for line in file with open(filename) as file:
    ...

这必须分成两行来写:

with open(filename) as file:
    for line in file:
        ...

这样做是好的,因为管理的上下文不是“我们在遍历文件”,而是“我们打开了一个文件”。所以再次问自己,上下文是什么?你到底在做什么?很可能,你管理的上下文并不是对资源的遍历。然而,如果你仔细看看你的具体问题,可能会发现确实有一种情况是生成器在管理一个上下文。希望理解真正的上下文能给你一些关于如何恰当地管理它的想法。

5

一般来说,你不会看到生成器作为上下文管理器,反之亦然,主要是因为它们解决的问题不同。上下文管理器的出现是为了提供一种干净简洁的方式,将可执行代码的作用范围限制在某个资源上。

有一个很好的理由说明为什么你可能想把实现了 __iter__() 的类和上下文管理器分开,那就是单一职责原则。单一职责原则的核心思想就是:

让一个类只做一件事,并把这件事做好

列表是可迭代的,但这是因为它们是一个集合。它们只管理自己所包含的状态,迭代只是另一种访问这些状态的方式。除非你需要通过迭代来访问某个对象的状态,否则我看不出有什么理由要把这两者混合在一起。即使在这种情况下,我也会尽量按照真正的面向对象风格将它们分开。

撰写回答