组合Python上下文管理器:一个难题

组合Python上下文管理器:一个难题,python,try-catch,with-statement,contextmanager,Python,Try Catch,With Statement,Contextmanager,我对如何将Python上下文管理器可以做的所有事情安排到适当的位置感到困惑 据我所知,可能用于构建上下文管理器的元素包括: 经常发生的事情 B:C需要一些准备工作 C:创建并建立上下文中使用的对象X D:在上下文开始之前,使用成功建立的X做一些事情 E:将X返回到上下文(供作为使用) F:当上下文结束时一切正常时,用X结束 G:在进入上下文之前,处理C和B中失败的后果 H:根据上下文处理失败的后果 我想我大致了解了这些元素在上下文管理器函数中的位置,但完全不知道如何在类中排列它们 是否有一个

我对如何将Python上下文管理器可以做的所有事情安排到适当的位置感到困惑

据我所知,可能用于构建上下文管理器的元素包括:

  • 经常发生的事情
  • B:C需要一些准备工作
  • C:创建并建立上下文中使用的对象X
  • D:在上下文开始之前,使用成功建立的X做一些事情
  • E:将X返回到上下文(供
    作为
    使用)
  • F:当上下文结束时一切正常时,用X结束
  • G:在进入上下文之前,处理C和B中失败的后果
  • H:根据上下文处理失败的后果
我想我大致了解了这些元素在上下文管理器函数中的位置,但完全不知道如何在类中排列它们

是否有一个上下文管理器函数和类的模板,可以显示这些元素在函数和(特别是)类中是否都存在?我在这里和其他地方查看了许多示例,但没有发现任何一个示例是全面的,许多示例使用的是实际代码,我无法始终映射到上面的每个构建块


我想我基本上理解了通过函数实现时上下文管理器的行为:

from contextlib import contextmanager     
@contextmanager
def log_file_open(oec_data, build_description, log_dir):
    # A: Something that always happens
    try:
        # B: Some stuff needed to make a_thing
        a_thing = establish_thing_in_a_way_that_might_fail() # C
        # D: Some things that happen using a_thing at context start
        yield a_thing # E
        # F: Wrap up with a_thing when all is well
    except:
        # G: Deal the consequences of failure in try or...
        # H: Deal the consequences of failure in context
    finally:
        # Could F go here instead?
例如,要打开一个文件,在成功打开和关闭时,应该将某些内容写入该文件,但如果出现问题,则应该清除该文件,我可以编写

from contextlib import contextmanager     
@contextmanager
def log_file_open(oec_data, build_description, log_dir):
    print('Entering context...')
    try:
        usable_file_name = get_some_name()
        a_thing =  open(usable_file_name, mode='w')
        a_thing.write('Logging context started.')
        yield a_thing
        a_thing.write('Logging context ended.')
    except:
        a_thing.close()
        os.remove(a_thing.name)
        raise
但我不确定这是否正确,我很困惑它如何映射到类中的
\uuuuu enter()\uuuuu
\uuuu exit()\uuuuuu
的使用。是(示意图):


您在生成上下文值时混淆了错误处理,而在上下文本身中混淆了错误处理。最好写下:

@contextmanager
def fn(...):
    value = ...      # A, B, C, D: setup
    try:
        yield value  # E: pass value to client
    except:          # or better, finally:
        ...          # F, H: cleanup
这样,您就知道您只处理源于客户端代码的异常,并且当您知道安装成功时,您就简化了清理代码。在设置代码中处理异常通常没有意义;您不希望客户端代码必须处理
None
上下文值。这意味着
\uuuu enter\uuuu
只是:

def __enter__(self):
    self.value = ...   # A, B, C, D: setup
    return self.value  # E: pass value to client
def __exit__(self, type, value, traceback):
    ...                # F, H: cleanup
    return False       # don't suppress any exception
如果
\uuuuuuuuuuuuuuuuuuuuu
引发异常,则不会调用
\uuuuuuuuuuuu

还要注意的是,
finally
好,除了
,除非您计划从客户机代码中抑制异常,这很少有用。所以
\uuuu退出\uuuu
就是:

def __enter__(self):
    self.value = ...   # A, B, C, D: setup
    return self.value  # E: pass value to client
def __exit__(self, type, value, traceback):
    ...                # F, H: cleanup
    return False       # don't suppress any exception

我认为你的理解基本上是正确的。上下文管理器是一个对象,它通过其
\uuuuuuuuuuuuuuuuuu
\uuuuuuuuuu
方法管理上下文。因此,
\uuuuu init\uuuu
中发生的事情在对象的生命周期内保持不变。 让我们看一个具体的例子:

class CMan(object):
    def __init__(self, *parameters):
        "Creates a new context manager"
        print "Creating object..."

    def __enter__(self):
        "Enters the manager (opening the file)"
        print "Entering context..."
        a_thing = self # Or any other relevant value to be used in this context
        print "Returning %s" % a_thing
        return a_thing

    def __exit__(self, type, value, traceback):
        "Exits the context"
        if type is None:
            print "Exiting with no exception -> Wrapping up"
            return
        print "Exiting with exception %s" % type
这将被用作:

>>> with CMan(1,2,3) as x:
...     print 1 + 1
Creating object...
Entering context...
Returning <__main__.CMan object at 0x02514F70>
2
Exiting with no exception -> Wrapping up
注意,在被0除错误的情况下,当
\uuu exit\uu
返回
True
时,错误不会传播。在其他情况下,它在退出上下文管理器后引发。您可以考虑调用上下文管理器:

>>> with X as x:
...     f(x)
等同于:

>>> x = X.__enter__()
>>> try:
...     exc = None
...     f(x)     
... except Exception as e:
...     exc = e
... finally:
...     handled = X.__exit__(exc)
...     if exc and not handled:
...         raise exc
当然,如果在方法
\uuuuu enter\uuuu
\uuuu exit\uuuuu
中引发异常,则应适当处理该异常,例如,如果生成
一个东西
可能失败。通过查找“Python with statement”,您可以在web上找到大量资源,这通常是您引用此模式的方式(尽管上下文管理器确实更正确)

>>> x = X.__enter__()
>>> try:
...     exc = None
...     f(x)     
... except Exception as e:
...     exc = e
... finally:
...     handled = X.__exit__(exc)
...     if exc and not handled:
...         raise exc