上下文管理器反向

Context Manager Reverse

我在 Python 方面处于中等水平,我最近一直在研究 Python 上下文管理器。我想颠倒进入和退出的顺序 运行。所以我写了这个上下文管理器:

class ReversibleContextManager(object):

    def __enter__(self, *args):
        print('d')
        return self

    def __exit__(self, *args):
        print('g')
        return self

    def __invert__(self):
        self.__enter__, self.__exit__ = self.__exit__, self.__enter__
        return self

向前工作得很好:

with ContextManager():
    print('o')

d
o
g

但是反过来,我们仍然得到:

with ~ContextManager():
    print('a')

d
o
g

如果我们按预期显式调用进入和退出函数,我们将得到:

with ReversibleContextManager() as c:
    c.__enter__()
    print('o')
    c.__exit__()

d
d
o
g
g

但是实例方法的顺序是相反的!

with ~ReversibleContextManager() as c:
    c.__enter__()
    print('o')
    c.__exit__()

d
g
o
d
g

所以看起来 with 语句调用使用绑定到 Class 的方法而不是实例(这是正确的术语吗?)。这是预期的吗?

所谓的是:

c = ReversibleContextManager()
c.__invert__()
ReversibleContextManager.__enter__(c)
...in context...
ReversibleContextManager.__exit__(c)

而不是我所期望的:

c = ReversibleContextManager()
c.__invert__()
c.__enter__()
...in context...
c.__exit__()

So it looks like the with statement calls using the method bound to the Class rather than the instance (is this the right terminology?). Is this expected?

当然可以。这就是 Python 通常查找特殊方法的方式。如果你有一个 class Foo 实现了 __str__print Foo 调用 type(Foo).__str__ 而不是 Foo.__str__

作为解决方法,您可以在 __invert__ 函数中创建反向 class,并 return 新 class.

的实例
class ReversibleContextManager(object):
    def __enter__(self, *args):
        print('enter')
        return self
    def __exit__(self, *args):
        print('exit')
        return self
    def __invert__(self):
        new = type("ReversibleContextManager",
                   (object,),
                   {'__enter__': self.__exit__,
                    '__exit__': self.__enter__})
        return new()

>>> with ReversibleContextManager() as f:
...     print("normal")
enter
normal
exit

>>> with ~ReversibleContextManager() as f:
...     print("reversed")
exit
reversed
enter

一种更冗长但非常明确的方法是使用两个辅助函数,并通过标志 reverse:

切换进入和退出时的哪个辅助函数
class ReversibleContextManager(object):

    def __init__(self, reverse=False):
        self.reverse = reverse

    def _enter(self, *args):
        print('d')
        return self

    def _exit(self, *args):
        print('g')
        return self

    def __enter__(self, *args):
        if self.reverse:
            return self._exit(*args)
        return self._enter(*args)

    def __exit__(self, *args):
        if self.reverse:
            return self._enter(*args)
        return self._exit(*args)

    def __invert__(self):
        self.reverse = True
        return self


>>> with ReversibleContextManager() as r:
    print('o')
d
o
g

>>> with ~ReversibleContextManager() as r:
    print('o')
g
o
d

>>> with ReversibleContextManager(reverse=True) as r:
    print('o')
g
o
d

另一种方式:

class ReversibleContextManager(object):

    inverted = False

    def __init__(self):
        if self.__class__.inverted:
            self.__invert__()
            self.__class__.inverted = False

    def __enter__(self, *args):
        print('d')
        return self

    def __exit__(self, *args):
        print('g')
        return self

    def __invert__(self):
        self.__class__.inverted = True
        self.__class__.__enter__, self.__class__.__exit__ = self.__exit__, self.__enter__
        return self