python3 / contextlib:将@ contextlib.contextmanager转换为获取/释放?

时间:2017-11-16 01:20:11

标签: python-3.x generator decorator contextmanager

我使用了一些第三方代码,可以通过@contextlib.contextmanager修饰的例程启用锁定。我还使用大型python3代码库,我们可以插入不同的锁定软件,只要我能够实现acquirerelease方法。

我试图在这个软件结构中使用第三方代码(不知道它是如何编写的)。

为了澄清我正在寻找的内容,假设其中一个第三方锁定例程被编写为标准@contextlib.contextmanager生成器,如下所示:

@contextlib.contextmanager
def lock(arg0, arg1):
    try:
        # This section of code corresponds to `acquire`.
        # Acquire a lock called 'lock', and then ...
        yield lock
    finally:
        # This section of code coresponds to `release`.
        # Do cleanup.

通常会像这样使用......

with third.party.lock(arg0, arg1):
    # Do stuff in this critical section

但正如我上面提到的,我想编写一个具有acquire方法和release方法的类,它使用third.party.lock,我想要通过现有的third.party.lock模块完成,无需重写。

换句话说,我想写一个看起来像这样的课......

class LockWrapper(object):

    def __init__(self):
        # initialization

    def acquire(self):
        # Use third.party.lock to obtain a lock.
        # ??? ??? ???

    def release(self):
        # I don't know what to do here. There is no yield
        # in the `finally` section of a normal
        # @contextlib.contextmanager decorated method/
        # ??? ??? ???

正如我在示例代码的评论中所述,我不知道如何让acquirerelease做任何有意义的事情。

看起来我必须从最初的third.party.lock模块中窃取代码才能实现这一目标,但我希望我能够在不必知道的情况下忽略这样做的方法有关此第三方代码的任何信息。

我运气不好吗?

非常感谢。

2 个答案:

答案 0 :(得分:0)

好的,我明白了。我的答案基于这里的一些代码:https://gist.github.com/icio/c0d3f7efd415071f725b

关键是这个enter_context辅助函数,它深入研究了上下文管理器的结构。它类似于该网站上同名的功能......

def enter_context(func, *args, **kwargs):
    def _acquire():
        with func(*args, **kwargs) as f:
            yield f
    acquire_gen = _acquire()
    def release_func():
        try:
            next(acquire_gen)
        except StopIteration:
            pass
    return acquire_gen, release_func

以下类为任何传递给构造函数的contextmanager函数实现acquirerelease。我将enter_context封装在这个类中......

class ContextWrapper(object):
    @staticmethod
    def enter_context(func, *args, **kwargs):
        def _acquire():
            with func(*args, **kwargs) as f:
                yield f
        acquire_gen = _acquire()
        def release_func():
            try:
                next(acquire_gen)
            except StopIteration:
                pass
        return acquire_gen, release_func
    def __init__(self, func, *args, **kwargs):
        self._acq, self._rel = self.__class__.enter_context(func, *args, **kwargs)
    def acquire(self):
        next(self._acq)
        return True
    def release(self):
        self._rel()
    # Traditional Dijkstra names.
    P = acquire
    V = release

然后,我可以在third.party.lock周围创建我的包装器,如下所示......

mylocker = ContextWrapper(third.party.lock, arg0, arg1)

...我可以按照以下方式致电acquirerelease ......

mylocker.acquire()
# or mylocker.P()
mylocker.release()
# or mylocker.V()

这是另一个如何使用这个类的例子......

class ThirdPartyLockClass(ContextWrapper):
    def __init__(self, arg0, arg1):
        # do any initialization
        super().__init__(third.party.lock, arg0, arg1)
    # Implementing the following methods is optional.
    # This is only needed if this class intends to do more
    # than the wrapped class during `acquire` or `release`,
    # such as logging, etc.
    def acquire(self):
        # do whatever
        rc = super().acquire()
        # maybe to other stuff
        return rc
    def release(self):
        # do whatever
        super().release()
        # maybe do other stuff
    P = acquire
    V = release

mylocker = ThirdPartyLockClass(arg0, arg1)
mylocker.acquire()
# or mylocker.P()
mylocker.release()
# or mylocker.V()

...或者我甚至可以在没有额外功能添加到第三方锁类的简单情况下执行以下操作...

class GenericLocker(ContextWrapper):
    def __init__(self, func, *args, **kwargs):
        super().__init__(func, *args, **kwargs)

mylocker = GenericLocker(third.party.lock, arg0, arg1)
mylocker.acquire()
# or mylocker.P()
mylocker.release()
# or mylocker.V()

答案 1 :(得分:0)

为什么不明确调用contextlib包装器的__enter____exit__

import contextlib

# Something to wrap
class Test:
    def __init__(self):
        print('Opening Test')
    def use(self):
        print('Using Test')
    def close(self):
        print('Closing Test')

# Wrap in the standard contextlib
@contextlib.contextmanager
def test():
    try:
        t = Test()
        yield t
    finally:
        t.close()

# Does it work?  yes...
with test() as t:
    t.use()

# Implementing acquire/release...
class TestWrapper(object):
    def __init__(self):
        pass
    def acquire(self):
        self.t = test()
        return self.t.__enter__()
    def release(self):
        self.t.__exit__(None,None,None)

# Also works...
tw = TestWrapper()
t = tw.acquire()
t.use()
tw.release()

输出:

Opening Test
Using Test
Closing Test
Opening Test
Using Test
Closing Test