如果建造和销毁费用昂贵,如何避免再次建立环境?

时间:2017-04-08 17:00:06

标签: python with-statement contextmanager

如果构建和销毁上下文很重,我如何检测到我已经拥有上下文并且不需要再次构建它?换句话说,我希望嵌套的上下文2什么也不做。

我可能需要在循环内多次调用上下文,我可以将整个循环放在with子句中,然后循环中的上下文知道它们已经在上下文中,然后跳过构建上下文,就好像内部with子句没有效果一样? (我想在以下示例中完全跳过上下文2)

from contextlib import contextmanager

@contextmanager
def myContext(i):

    print 'build context', i

    try:
        yield
    finally:
        print 'exiting context',i

def func():
    print 'call func'

with myContext(1):

    print 'outside'

    with myContext(2):
        func()

代码输出:

build context 1
outside
build context 2
call func
exiting context 2
exiting context 1

3 个答案:

答案 0 :(得分:1)

使用单例的上下文管理器类而不是在函数上使用contextmanager装饰器可能是有意义的。这与kennytm使用全局变量的答案没有根本的不同,但是我的版本在类变量而不是常规全局变量中隐藏了全局状态。

class MyContext(object):
    _instance = None

    def __new__(cls, i):
        if cls._instance is None:
            cls._instance = super(cls, MyContext).__new__(cls, i)
            cls._instance.depth = 0
        return cls._instance

    def __init__(self, i):
        self.i = i

    def __enter__(self):
        self.depth += 1
        if self.depth == 1:
            self.some_expensive_value = expensive_calculation(self.i)
        return self       # or maybe return self.some_expensive_value?

    def __exit__(self, exec_type, exec_value, traceback):
        self.depth -= 1
        if self.depth == 0:
            self.expensive_value.close()   # clean up value if necessary

这种方法不是线程安全的,但你可以通过添加一个锁定self.depth的更改和检查来实现。昂贵的计算仅针对上下文管理器的任意数量嵌套调用的最外层运行。

答案 1 :(得分:0)

您可以创建全局引用计数:

_my_context_call_count = 0

@contextmanager
def my_context(i):
    global _my_context_call_count

    if _my_context_call_count == 0:
        print 'build context', i
    _my_context_call_count += 1

    try:
        yield

    finally:
        _my_context_call_count -= 1
        if _my_context_call_count == 0:
            print 'exiting context', i

(请注意,这是不是线程安全的,但如果您的程序是单线程的,则无关紧要。)

答案 2 :(得分:0)

我使用元类将@ Blckknght的回答与来自Method 3Creating a singleton in Python结合起来。

from abc import ABCMeta, abstractmethod
from functools import wraps
import time

class Singleton(ABCMeta):
    _instance = None

    def __call__(cls, *args, **kwargs):
        if not hasattr(cls, "_instance") or cls._instance is None:
            cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instance


class ContextDecorator(object):

    __metaclass__ = Singleton

    def __init__(self, *args, **kwargs):
        self.args = args
        self.__dict__.update(kwargs)
        self._built = False
        self._contextExists = False

    @abstractmethod
    def _build(self):
        pass

    @abstractmethod
    def _destory(self):
        pass

    @classmethod
    def clear_singleton(cls):
        cls._instance = None

    def __enter__(self):
        if not self._built:
            self._build()
            self._built = True
            print 'call _build first time'
        else:
            print 'skip _build'
            self._contextExists = True

        return self

    def __exit__(self, typ, val, traceback):
        if not self._contextExists:
            self._destory()
            self.clear_singleton()
            # self._contextExists=False
            print 'call _destory first time'
        else:
            print 'skip _destory'
            self._contextExists = False

    def __call__(self, f):
        self.function = f

        @wraps(f)
        def wrapper(*args, **kw):
            with self:
                try:
                    return f(*args, **kw)
                except:
                    raise
        return wrapper


class CustomContext(ContextDecorator):

    def __init__(self, *args, **kwargs):
        super(CustomContext, self).__init__(*args, **kwargs)

    def _build(self):
        pass

    def _destory(self):
        pass




print 'context managere test'

with CustomContext():
    for i in range(3):
        with CustomContext():
            time.sleep(0.01)


print '-' * 10
print 'decorator test'


@CustomContext()
@CustomContext()
def test():
    print 'in side test func'


test()

输出

context managere test
call _build first time
skip _build
skip _destory
skip _build
skip _destory
skip _build
skip _destory
call _destory first time
----------
decorator test
call _build first time
skip _build
in side test func
skip _destory
call _destory first time