为什么生成器不是上下文管理器?
生成器可以用来管理一些资源,比如在上下文管理器里面使用 yield
。当你调用生成器的 close()
方法(或者发生异常时),这个资源就会被释放。
因为有时候我们会忘记在最后调用 close()
,所以我觉得使用上下文管理器来处理这个问题是很明显的选择,这样也能处理可能出现的异常。我知道可以用 contextlib.closing
来做到这一点,但如果能直接在 with
语句中使用生成器,不是更好吗?
有没有什么理由让生成器 不 能成为上下文管理器呢?
2 个回答
就像Wheaties说的,你希望类只做“一件事,并且做到最好”。特别是对于上下文管理器,它们就是在管理一个上下文。那么,问问自己,这里的上下文是什么呢?大多数情况下,就是在打开一个资源。之前我问过关于如何使用队列和上下文管理器的问题,得到的回答基本上是队列作为上下文没有意义。然而,“在一个任务中”才是真正的上下文,这样做上下文管理器就有意义了。
另外,没有可以重复使用的with
语句。举个例子,我不能在一行代码中打开一个文件并遍历它,像这样:
for line in file with open(filename) as file:
...
这必须分成两行来写:
with open(filename) as file:
for line in file:
...
这样做是好的,因为管理的上下文不是“我们在遍历文件”,而是“我们打开了一个文件”。所以再次问自己,上下文是什么?你到底在做什么?很可能,你管理的上下文并不是对资源的遍历。然而,如果你仔细看看你的具体问题,可能会发现确实有一种情况是生成器在管理一个上下文。希望理解真正的上下文能给你一些关于如何恰当地管理它的想法。
一般来说,你不会看到生成器作为上下文管理器,反之亦然,主要是因为它们解决的问题不同。上下文管理器的出现是为了提供一种干净简洁的方式,将可执行代码的作用范围限制在某个资源上。
有一个很好的理由说明为什么你可能想把实现了 __iter__()
的类和上下文管理器分开,那就是单一职责原则。单一职责原则的核心思想就是:
让一个类只做一件事,并把这件事做好
列表是可迭代的,但这是因为它们是一个集合。它们只管理自己所包含的状态,迭代只是另一种访问这些状态的方式。除非你需要通过迭代来访问某个对象的状态,否则我看不出有什么理由要把这两者混合在一起。即使在这种情况下,我也会尽量按照真正的面向对象风格将它们分开。