如果构建和销毁上下文很重,我如何检测到我已经拥有上下文并且不需要再次构建它?换句话说,我希望嵌套的上下文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
答案 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 3的Creating 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