嵌套Python上下文管理器

Nei*_*l G 7 python contextmanager

这个问题中,我定义了一个包含上下文管理器的上下文管理器.完成此嵌套最简单的正确方法是什么?我最后打电话来self.temporary_file.__enter__()self.__enter__().但是,self.__exit__我很确定我必须调用self.temporary_file.__exit__(type_, value, traceback)finally块,以防引发异常.如果出现问题,我应该设置type_,value和traceback参数self.__exit__吗?我检查过contextlib,但找不到任何实用程序来帮助解决这个问题.

来自问题的原始代码:

import itertools as it
import tempfile

class WriteOnChangeFile:
    def __init__(self, filename):
        self.filename = filename

    def __enter__(self):
        self.temporary_file = tempfile.TemporaryFile('r+')
        self.f = self.temporary_file.__enter__()
        return self.f

    def __exit__(self, type_, value, traceback):
        try:
            try:
                with open(self.filename, 'r') as real_f:
                    self.f.seek(0)
                    overwrite = any(
                        l != real_l
                        for l, real_l in it.zip_longest(self.f, real_f))
            except IOError:
                overwrite = True
            if overwrite:
                with open(self.filename, 'w') as real_f:
                    self.f.seek(0)
                    for l in self.f:
                        real_f.write(l)
        finally:
            self.temporary_file.__exit__(type_, value, traceback)
Run Code Online (Sandbox Code Playgroud)

Pet*_*rin 10

创建上下文管理器的简单方法是contextlib.contextmanager.像这样的东西:

@contextlib.contextmanager
def write_on_change_file(filename):
    with tempfile.TemporaryFile('r+') as temporary_file:
        yield temporary_file
        try:
             ... some saving logic that you had in __exit__ ...
Run Code Online (Sandbox Code Playgroud)

然后用with write_on_change_file(...) as f:.声明
的正文with将被执行"而不是" yield.如果你想捕捉身体中发生的任何异常,请将yield自身包裹在一个try块中.

临时文件将始终正确关闭(当其with块结束时).

  • 使用`@intextlib.contextmanager`很方便,但是仍然有一些情况是使用手动定义的`__enter__`和`__exit__`方法的类是合适的.你有这方面的建议吗? (3认同)

Con*_*tor 5

contextlib.contextmanager对于函数来说效果很好,但是当我需要一个类作为上下文管理器时,我使用以下实用程序:

class ContextManager(metaclass=abc.ABCMeta):
  """Class which can be used as `contextmanager`."""

  def __init__(self):
    self.__cm = None

  @abc.abstractmethod
  @contextlib.contextmanager
  def contextmanager(self):
    raise NotImplementedError('Abstract method')

  def __enter__(self):
    self.__cm = self.contextmanager()
    return self.__cm.__enter__()

  def __exit__(self, exc_type, exc_value, traceback):
    return self.__cm.__exit__(exc_type, exc_value, traceback)
Run Code Online (Sandbox Code Playgroud)

这允许使用来自 的生成器语法声明 contextmanager 类@contextlib.contextmanager。它使得嵌套 contextmanager 变得更加自然,而无需手动调用__enter____exit__。例子:

class MyClass(ContextManager):

  def __init__(self, filename):
    self._filename = filename

  @contextlib.contextmanager
  def contextmanager(self):
    with tempfile.TemporaryFile() as temp_file:
      yield temp_file
      ...  # Post-processing you previously had in __exit__


with MyClass('filename') as x:
  print(x)
Run Code Online (Sandbox Code Playgroud)

我希望这是在标准库中......