【问题标题】:Nesting Python context managers嵌套 Python 上下文管理器
【发布时间】:2012-01-03 23:34:41
【问题描述】:

this question 中,我定义了一个包含上下文管理器的上下文管理器。完成这种嵌套的最简单正确的方法是什么?我最终在self.__enter__() 中致电self.temporary_file.__enter__()。但是,在self.__exit__ 中,我很确定我必须在 finally 块中调用self.temporary_file.__exit__(type_, value, traceback),以防引发异常。如果self.__exit__ 出现问题,我应该设置 type_、value 和 traceback 参数吗?我检查了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)

【问题讨论】:

    标签: python contextmanager


    【解决方案1】:

    创建上下文管理器的简单方法是使用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__ ...
    

    然后使用with write_on_change_file(...) as f:
    with 语句的主体将“代替”yield 执行。如果您想捕获正文中发生的任何异常,请将 yield 本身包装在 try 块中。

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

    【讨论】:

    • 这真是太好了。我将把这个问题留一会儿,以防这个问题产生任何其他好的答案。
    • 使用@contextlib.contextmanager 很方便,但仍有一些情况适合使用手动定义的__enter____exit__ 方法的类。您对此有什么建议吗?
    • 好吧,在更方便的时候做——例如当对象需要做的不仅仅是作为一个上下文管理器(虽然在这种情况下你也应该考虑添加一个@contextlib.contextmanager 方法) .
    • 我唯一的建议是从对称的角度将self.temporary_file = tempfile.TemporaryFile('r+') 移动到init 语句中。
    【解决方案2】:

    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)
    

    这允许使用来自@contextlib.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)
    

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

    【讨论】:

    • @NeilG,根据@Zearin 的评论,在某些情况下需要使用类而不是函数,在这种情况下不能使用contextlib.contextmanager。此实用程序允许将contextlib.contextmanager 生成器语法与类一起使用。这使得嵌套上下文管理器更加自然,无需存储 x.__enter__() 并手动调用 x.__exit__()
    • 是的,但这并不能回答我的问题,即关于嵌套上下文管理器的问题。此外,无论何时实现__enter____exit__,都应始终调用super,以防您的类用于协作继承。
    • @NeilG,我更新了代码 sn-p 以更好地匹配问题。基本上,它与 Petr Viktorin 的答案相同,但与类一起工作。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-08
    • 1970-01-01
    • 1970-01-01
    • 2019-06-18
    • 1970-01-01
    相关资源
    最近更新 更多