如何将子上下文的管理委托给父上下文

10 投票
2 回答
1353 浏览
提问于 2025-04-18 09:53
def __enter__()
def __exit__()

假设我们有一个叫做'A'的类,它本身就是一个上下文管理器,所以它实现了相应的接口。这样,客户端代码可以直接使用with语句来创建'A'对象。

现在,我们还有另一个类'B',它封装了其他功能,同时也使用了'A'对象。

如果我们想让'B'也作为一个上下文管理器,那么管理它的'A'实例的正确方法是什么呢?

在'B'的__enter____exit__方法中,应该调用它的'A'对象的__enter____exit__方法吗?还是有更好的方法呢?

为了给出一个具体的例子(这并不是我在应用中使用的,只是我想到的第一个非抽象的例子),我们考虑两个类:

  • DatabaseConnection(数据库连接)
  • DatabaseConnectionPool(数据库连接池)

单独使用一个DatabaseConnection是有效的,因此DatabaseConnection实现了上下文管理器接口。

DatabaseConnectionPool使用了多个DatabaseConnection以及其他一些东西。使用DatabaseConnectionPool(也就是"with"语句)应该能够对它的DatabaseConnection实例进行设置和清理(以及它可能想做的其他事情)。

更新:我写了一些测试代码,希望能得到以下输出:

Enter invoked on Outer
Enter invoked on Inner
Within outer context...
do_foo invoked!
Still using outer...
Exit invoked on inner
Exit invoked on outer
Done using outer

但我得到了以下结果:

Enter invoked on Outer
Enter invoked on Inner
Exit invoked on inner
Within outer context...
do_foo invoked!
Still using outer...
Exit invoked on outer
Done using outer

代码:


class Inner(object):
  def __enter__(self):
    print "Enter invoked on Inner"
    return self

  def __exit__(self, typ, val, tb):
    print "Exit invoked on inner"

  def do_foo(self):
    print "do_foo invoked!"

class Outer(object):
  def __init__(self):
    self._inner = Inner()

  def __enter__(self):
    print "Enter invoked on Outer"

    with self._inner as ctx:
      return self

  def __exit__(self, typ, val, tb):
    print "Exit invoked on outer"

with Outer() as outer:
  print "Within outer context..."
  outer._inner.do_foo()
  print "Still using outer..."

print "Done using outer"

有没有什么想法可以让这个工作正常?

2 个回答

0

这其实是一个设计方面的问题,需要做一些判断。我建议你考虑以下几点:

一个连接池的类实例可以提供一个上下文,这样程序就可以根据需要使用数据库连接实例。不过,这样做会比简单地对每个数据库实例调用 __enter____exit__ 要复杂一些,我觉得你不应该尝试这样做,因为它们并不是为了这个目的设计的。在这种情况下,我建议你直接使用数据库实例的上下文管理器。

如果你想使用它们的上下文管理器,可以参考下面的写法:

def __enter__(self):
    for db in self.pool:
        with db as d:
            d.transaction()

def __exit__(self, type, value, traceback):
    pass 

但是如果你想自己处理错误,可以参考下面的写法:

def __enter__(self):
    for db in self.pool:
        try:
            db.connection()
            # ... write and transact here

def __exit__(self, type, value, traceback):
    if type is None: # no errors, so close as normal
        for db in self.pool:
            db.close()
    # ... more code here for proper error handling
1

顺便说一下,我也遇到过类似的设计问题,所以我在我的“子”类里写了退出和进入的函数,而“父”类则只作为一个上下文管理器。

(如果我理解你的问题没错的话,你是在问‘我怎么才能优雅地让所有在上下文管理器类中使用的类在关闭父上下文管理器时也能正确关闭’)

撰写回答