如何从Python中的对象方法中惯用地打开多个托管资源

时间:2019-02-12 09:07:33

标签: python resources yield contextmanager

构造对象以打开多个(上下文托管)资源并使用这些资源的最Python方式是什么?

我有一个可以打开多个托管资源的类,然后可以在类方法中对其进行操作。

例如,如果我必须同时打开与本地缓存数据库和Web服务器的连接(例如,首先检查缓存中的数据,然后从服务器中拉出)。

我已经能够使用yield语句想出一些代码来管理资源,但这似乎不是很直观。在Python中,是否有解决此问题的规范方法?

最小示例:

from contextlib import contextmanager

@contextmanager
def resource_A():
    print('opening resource A...')
    a = 'resource_A'
    yield a
    print('closing resource A...')

@contextmanager
def resource_B():
    print('opening resource B...')
    b = 'resource_B'
    yield b
    print('closing resource B...')

class ResourceManager(object):
    def opened(self):
        self.resources = self._resources()
        self.a, self.b = next(self.resources)
    def closed(self):
        try:
            next(self.resources)
        except StopIteration:
            del self.resources
            del self.a
            del self.b
    def _resources(self):
        with resource_A() as a, resource_B() as b:
            yield a, b
    def processing(self):
        print('doing something with resource_A and resource_B...')
    def __enter__(self):
        self.opened()
        return self
    def __exit__(self, ex_type, ex_val, traceback):
        self.closed()

打开和关闭

>>> r = ResourceManager()
>>> r.opened()
opening resource A...
opening resource B...
>>> r.a
'resource_A'
>>> r.b
'resource_B'
>>> r.closed()
closing resource B...
closing resource A...

与上下文管理器一起使用

>>> with ResourceManager() as r:
...     r.processing()
...
opening resource A...
opening resource B...
doing something with resource_A and resource_B...
closing resource B...
closing resource A...

上面的代码似乎可以正常工作,但是看起来不是很直观。具体来说,产量下一个习语似乎有点难以消化。

是否有更好的方法来打开/关闭多个托管资源,这些资源随后将在Python的类方法中使用?

1 个答案:

答案 0 :(得分:1)

  1. 我认为,ExitStack将使您的代码更容易
  2. IMO,明确使用__enter____exit__next(...)更易读
  3. 实际上与CM无关,但是惯用 python代码的很大一部分都包含命名。 openedclosed读作应返回布尔值的属性,即r.opened -> True。这就是大多数人对界面的期望。另一方面,动作应拼写为动词,例如openclose

具有上述想法的简单示例:

class ResourceManager(object):
    def open(self):
        self.stack = ExitStack()
        self.a = self.stack.enter_context(resource_A())
        self.b = self.stack.enter_context(resource_B())
    def close(self):
        self.stack.close()

    def processing(self):
        print('doing something with resource_A and resource_B...')
    def __enter__(self):
        self.open()
        return self
    def __exit__(self, ex_type, ex_val, traceback):
        self.close()