关于python:在类的所有成员上调用__exit__

Call __exit__ on all members of a class

是否有一种Pythonic方法可以自动__exit__类的所有成员?

1
2
3
4
5
6
7
8
9
10
11
12
class C:
    def __init__(self):
        self.a = open('foo')
        self.b = open('bar')

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        # Is it correct to just forward the parameters here?
        self.a.__exit__(self, exc_type, exc_value, traceback)
        self.b.__exit__(self, exc_type, exc_value, traceback)

如果不在ab上手动调用__exit__,我可以这样做吗? 我甚至正确地打电话给__exit__吗?

假设我拥有的资源不是像示例中那样的file,并且没有像closedestroy这样的方法。 在__enter____exit__之上实现这样的方法可能是一种好习惯吗?


正如我在评论中提到的那样:

I think most helpfull here will be : contextlib.ExitStack

您可以在__init__中将此对象创建为您自己的类的成员。
然后使用enter_context(cm)将所有依赖的上下文管理器添加到其中,其中cm是context_manager

1
2
3
4
5
def __init__(self):
    self.exit_stack = contextlib.ExitStack()
    self.exit_stack.__enter__()
    self.exit_stack.enter_context(open('foo'))
    ....

清除所有依赖上下文只需在__exit__中调用此堆栈的出口。

或者更好的只是子类ExitStack并在init中调用enter_context


编写自己的__enter____exit__函数往往不是一个好主意。您需要了解在子__exit__期间发生异常时应该发生什么,或者如果__exit__返回真实值,则意味着什么。

通常,更好的方法是编写基于生成器的上下文管理器。例如。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from contextlib import contextmanager

class A:
    def __init__(self, filename0, filename1, file0, file1):
        self.filename0 = filename0
        self.filename1 = filename1
        self.file0 = file0
        self.file1 = file1

    @classmethod
    @contextmanager
    def create(cls, filename0, filename1):
        with open(filename0) as file0, \
                open(filename1) as file1:
            yield cls(filename0, filename1, file0, file1)

with A.create('file0.txt', 'file1.txt') as a:
    a.do_something()

这将按定义的顺序打开子上下文管理器,并按照定义的顺序自动关闭它们,传播异常并正确返回值。


要为实例成员提供上下文管理器功能,可以执行以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MemberManager:
    managed_member_names = ('a', 'b', 'c')
    def __init__(self, a, b, c):
        self.a, self.b, self.c = a, b, c
    def __enter__(self):
        # yield statement means this enter method returns a generator
        for i in (getattr(self,n) for n self.managed_member_names):
            with open(i, mode="w") as x:
                # yield prevents the context manager from exiting
                yield x
    def __exit__(self, exc_type, exc_value, traceback):
        # all items will be closed by __enter__ context manager; nothing needed here
        pass

mm = MemberManager(fname1, fname2, fname3)
with mm as open_members:
    # open_members is a generator/iterator
    for member in open_members:
        member.write("foo")

但请注意,您不能这样做:

1
2
3
with mm as open_members:
    open_member_list = list(open_members)
    open_member_list[0].write("foo") # ValueError: I/O operation on closed file.

open_members迭代器必须保持无法使用,以使当前文件保持打开状态。


__enter____exit__特殊功能几乎从不手动调用。在进入和离开with块语句时分别调用。所以如果你使用类似的东西:

1
2
3
with C() as c:
    # stuff
# other stuff

你称这些神奇的功能。在你的情况下,我将调用__enter__中的文件open函数和__exit__中相应的close函数

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class C:
    def __enter__(self):
        self.a = open('foo')
        self.b = open('bar')
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.a.close()
        self.b.close()

    def readline_a(self):
      return self.a.readline()

    def readline_b(self):
      return self.b.readline()

with C() as c:
  print(c.readline_a())
  print(c.readline_b())

打印每个文件foobar的第一行