通常需要多个具有资源的对象明确释放,例如两个文件; 当任务局限在一个函数内时,可以使用嵌套的 with
块轻松实现这一点,或者更好的是,使用一个带有多个 with_item
子句的单个 with
块:
with open('in.txt', 'r') as i, open('out.txt', 'w') as o:
# do stuff
然而,我仍然很难理解当这些对象不仅仅是函数作用域内的本地对象,而是由类实例所拥有时,它们应该如何工作 - 换句话说,上下文管理器是如何组合的。
理想情况下,我想做这样的事情:
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)
但是它既冗长又不安全,容易在构造函数中发生异常。搜索一段时间后,我找到了这篇2015年的博客文章,它使用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
这相当令人满意,但我对以下事实感到困惑:
- 在文档中找不到关于此用法的任何信息,所以似乎不是解决此问题的“官方”方法;
- 一般来说,我发现很难找到有关此问题的信息,这让我想到我正在尝试将非pythonic的解决方案应用于该问题。
一些额外的背景信息:我主要使用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 ++的模式,而根本问题有一个完全不同的解决方案,我想不出来。
因此,总结一下:对于拥有需要清理的对象的对象成为上下文管理器本身的问题,有什么Pythonic的解决方案,可以正确调用其子项的 __enter__ /
__exit__ ?
ExitStack
解决方案非常符合Pythonic风格。 - Edward Minnix