【发布时间】:2018-08-02 08:47:53
【问题描述】:
对于某些任务,通常需要多个具有显式释放资源的对象 - 例如,两个文件;当任务是使用嵌套 with 块的函数的本地任务时,这很容易完成,或者 - 更好的是 - 单个 with 块和多个 with_item 子句:
with open('in.txt', 'r') as i, open('out.txt', 'w') as o:
# do stuff
OTOH,当此类对象不仅是函数范围的本地对象,而是由类实例拥有时,我仍然很难理解它应该如何工作 - 换句话说,上下文管理器是如何组成的。
理想情况下,我想做这样的事情:
class Foo:
def __init__(self, in_file_name, out_file_name):
self.i = WITH(open(in_file_name, 'r'))
self.o = WITH(open(out_file_name, 'w'))
并让Foo 本身变成处理i 和o 的上下文管理器,这样当我这样做时
with Foo('in.txt', 'out.txt') as f:
# do stuff
self.i 和 self.o 会按照您的预期自动处理。
我修改了一些东西,例如:
class Foo:
def __init__(self, in_file_name, out_file_name):
self.i = open(in_file_name, 'r').__enter__()
self.o = open(out_file_name, 'w').__enter__()
def __enter__(self):
return self
def __exit__(self, *exc):
self.i.__exit__(*exc)
self.o.__exit__(*exc)
但是对于构造函数中发生的异常,它既冗长又不安全。找了一阵子,找到this 2015 blog post,它使用contextlib.ExitStack得到了和我想要的很相似的东西:
class Foo(contextlib.ExitStack):
def __init__(self, in_file_name, out_file_name):
super().__init__()
self.in_file_name = in_file_name
self.out_file_name = out_file_name
def __enter__(self):
super().__enter__()
self.i = self.enter_context(open(self.in_file_name, 'r')
self.o = self.enter_context(open(self.out_file_name, 'w')
return self
这很令人满意,但我对以下事实感到困惑:
- 我在文档中没有找到关于此用法的任何信息,因此它似乎不是解决此问题的“官方”方式;
- 总的来说,我发现很难找到有关此问题的信息,这让我觉得我正在尝试对问题应用非 Python 的解决方案。
一些额外的上下文:我主要在 C++ 中工作,对于这个问题,块范围的情况和对象范围的情况没有区别,因为这个一种清理在析构函数内部实现(想想__del__,但确定性地调用),析构函数(即使没有明确定义)自动调用子对象的析构函数。所以两者都是:
{
std::ifstream i("in.txt");
std::ofstream o("out.txt");
// do stuff
}
和
struct Foo {
std::ifstream i;
std::ofstream o;
Foo(const char *in_file_name, const char *out_file_name)
: i(in_file_name), o(out_file_name) {}
}
{
Foo f("in.txt", "out.txt");
}
按照您通常的需要自动执行所有清理工作。
我正在寻找 Python 中的类似行为,但我再次担心我只是在尝试应用来自 C++ 的模式,而根本问题有一个我想不到的完全不同的解决方案的。
所以,总结一下:对于拥有需要清理的对象的对象本身成为上下文管理器并正确调用其子级的__enter__/__exit__ 的问题,Pythonic 的解决方案是什么?
【问题讨论】:
-
我会说 ExitStack 的解决方案非常 Pythonic。
-
@BrenBarn:很高兴知道,但我仍然有点害怕,因为我认为在随机博客中只提到了这个解决方案,而不是在官方文档中将是一个相当普遍的问题。这就是让我感到困惑的原因。
-
我不知道你为什么期望它出现在官方文档中。一般来说,官方文档只记录事物的工作方式,而不是它们的用途。官方文档中没有解释大量常见问题的解决方案。 Here 是一个相关问题,在对他的回答的评论中,Martijn Pieters 还建议将 ExitStack 子类化以实现看起来相关的目的。
-
一个上下文管理器,旨在简化以编程方式组合其他上下文管理器和清理功能,尤其是那些可选的或由输入数据驱动的那些。我觉得文档表明
ExitStack解决方案是完美的 Pythonic。
标签: python contextmanager