【问题标题】:Call __exit__ on all members of a class对类的所有成员调用 __exit__
【发布时间】:2018-09-27 06:43:00
【问题描述】:

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

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)

我可以在不手动调用__exit__ 上的ab 的情况下执行此操作吗?我是否正确地调用了__exit__

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

【问题讨论】:

  • 你见过this answer吗?
  • @arsho 是的,我有。
  • 我认为这里最有帮助的是:contextlib.ExitStack
  • @Take_Care_ 嘿,这看起来很完美!您能否将您的评论扩展为一个完整的答案,以便我接受?
  • 或者在python-3.7中你可以使用contextlib.AsyncExitStack

标签: python python-3.x


【解决方案1】:

正如我在评论中提到的:

我认为这里最有帮助的是:contextlib.ExitStack

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

def __init__(self):
    self.exit_stack = contextlib.ExitStack()
    self.exit_stack.__enter__()
    self.exit_stack.enter_context(open('foo')) 
    ....

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

或者最好只是继承 ExitStack 并在 init 中调用 enter_context

【讨论】:

    【解决方案2】:

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

    通常最好只编写一个基于生成器的上下文管理器。例如。

    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()
    

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

    【讨论】:

      【解决方案3】:

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

      with C() as c:
          # stuff
      # other stuff
      

      你称之为魔法函数。在你的情况下,我会在__enter__ 中调用文件open 函数,在__exit__ 中调用相应的close 函数

      例如:

      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的第一行

      【讨论】:

      • 如果你的资源只需要内存分配,最好依靠垃圾回收器来释放资源。在其他情况下,我想总会有某种方法说“不再需要这些东西”。你有一个确切的例子吗?
      【解决方案4】:

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

      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")
      

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

      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 迭代器必须保持未用尽,当前文件才能保持打开状态。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-06-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-12-21
        相关资源
        最近更新 更多