【问题标题】:what is the pythonic way to inherit context manager继承上下文管理器的pythonic方法是什么
【发布时间】:2015-09-20 06:36:30
【问题描述】:

Python 教我们使用__enter____exit__ 对对象进行清理。 如果我需要创建一个使用对象的对象必须使用上下文管理器怎么办?想象一下:

from database1 import DB1
from database2 import DB2

通常,它们会这样使用:

with DB1() as db1, DB2() as db2:
    db1.do_stuff()
    db2.do_other_stuff()

无论发生什么,db1db2 都会运行它们的 __exit__ 函数,并清理连接、刷新等。

当我把所有这些都放在一个班级里时,我会怎么做? 对吗?这显然不对,db1db2 的上下文管理器运行在块的末尾,如 cmets 中所指出的那样。

class MyApp(object):
    def __enter__(self):
        with DB1() as self.db1, DB2() as self.db2:
            return self
    def __exit__(self, type, value, traceback):
        self.db1.__exit__(self, type, value, traceback)
        self.db2.__exit__(self, type, value, traceback)

我什至考虑过这样做:这看起来是个好主意,实际上(经过一些清理之后):

class MyApp(object):
    def __init__(self):
        self.db1 = DB1()
        self.db2 = DB2()
    def __enter__(self):
        self.db1.__enter__()
        self.db2.__enter__()
        return self
    def __exit__(self, type, value, traceback):
        try:
            self.db1.__exit__(self, type, value, traceback)
        except:
            pass
        try:
            self.db2.__exit__(self, type, value, traceback)
        except:
            pass

编辑:修复代码。

【问题讨论】:

  • 您的第一次尝试没有意义 - with DB1() ... 块结束之前 MyApp.__enter__ 完成; self.db1MyApp.__exit__ 开始之前很久就已经拥有__exit__ed。
  • 第二个选项几乎不错,如果添加异常处理就可以了(这样如果第二个__enter__ 或第一个__exit__ 失败,另一个连接不会挂起。也就是说,考虑@ 987654321@.
  • @jonrsharpe,我知道这就是我一开始问的原因。

标签: python class python-2.7 contextmanager


【解决方案1】:

我会选择第二种解决方案,但也会处理数据库错误:

import sys

class MyApp(object):
    def __init__(self):
        self.db1 = DB1()
        self.db2 = DB2()
    def __enter__(self):
        self.db1.__enter__()
        try:
            self.db2.__enter__()
        except:
            self.db1.__exit__(None, None, None) # I am not sure with None
            raise
        return self
    def __exit__(self, type, value, traceback):
        try:
            self.db1.__exit__(self, type, value, traceback)
        finally:
            self.db2.__exit__(self, type, value, traceback)

第一个在__enter__ 中调用__exit__ 因为with - 所以,不起作用。

编辑:同时查看answer by @Ming。在许多情况下,它会更短。

【讨论】:

  • 您不应该将变量设置为类中的无吗?
  • 你的 __enter__ 函数没有 return self
  • @user37203 谢谢,已更正。 @JohnRuddell 你能解释一下为什么吗?
  • 在上下文中,首先运行__init__,然后运行__enter__。也许与运行顺序有关的混乱?
【解决方案2】:

取决于您想要实现的总体目标。一种可能性是构建单独的上下文管理器,然后将它们与标准库的contextlib.nested 结合起来。这将为您提供一个对象,其行为类似于您的示例 MyApp,但以 DRY(不要重复自己)方式利用现有标准库。

【讨论】:

  • 这很好!你能添加源代码吗?可以用这个类吗?
【解决方案3】:

大多数上下文管理器都可以使用@contextmanager 装饰器来编写。您编写一个具有一个产量的函数,在产量之前是您的“进入”功能,在产量之后是您的“退出”功能。由于生成器的实现方式,如果 yield 在 with 语句中,则 with 语句不会在 yield 处退出。

例如。

from contextlib import contextmanager

class SomeContextManager:
    def __init__(self, name):
        self.name = name
    def __enter__(self):
        print("enter", self.name)
        return self
    def __exit__(self, ex_type, value, traceback):
        print("exit", self.name)

class SomeContextManagerWrapper:
    def __init__(self, *context_managers):
        self.context_managers = context_managers
    @property
    def names(self):
        return [cm.name for cm in self.context_managers]

@contextmanager
def context_manager_combiner():
    print("context_manager_combiner entering")
    with SomeContextManager("first") as a, SomeContextManager("second") as b:
        yield SomeContextManagerWrapper(a, b)
    print("context_manager_combiner exiting")

with context_manager_combiner() as wrapper:
    print("in with statement with:", wrapper.names)

输出:

context_manager_combiner entering
enter first
enter second
in with statement with: ['first', 'second']
exit second
exit first
context_manager_combiner exiting

【讨论】:

  • 这是最好的答案,但我需要一些可以在类实例化中应用的东西。我不太确定如何使用contextmanager
  • 您可以在包装数据库的 yield 语句中返回一个类,该类可以具有用于操作数据库的关联方法。
猜你喜欢
  • 1970-01-01
  • 2019-07-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-29
  • 1970-01-01
  • 2018-05-23
相关资源
最近更新 更多