我正在编写一个可以与with
语句一起使用的类:
with Context() as context:
if context:
...
该类有一个enter
函数,只能调用一次并返回一个布尔值。我想阻止它被调用两次(例如with Context().enter() as context
):
class Context(object):
def __init__(self):
self.ENTER_HAS_BEEN_CALLED = False
def __enter__(self):
return self.enter()
def __exit__(self):
self.exit()
def enter(self):
"""Do things once and only once
Returns boolean, not self
"""
if self.ENTER_HAS_BEEN_CALLED:
# not sure what to do here, return?
# call self.exit()?
self.ENTER_HAS_BEEN_CALLED = True
# do things that should only be done once
value = True # or False
return value
def exit(self):
pass
这是防止函数被调用两次的正确方法吗?我想要返回值,并且还允许此代码工作:
context_manager = Context()
context = context_manager.enter()
if context: ...
context_manager.exit()
答案 0 :(得分:2)
你很接近,但是,如果在使用装饰器设置标志后已经调用了contextmanager,则可以简单地引发错误。编写以下代码是为了使enter
和__enter__
都可以按原设置返回单独的变量(True
和self
),并与上下文管理器的一般概念保持一致:
def control_manager(f):
def wrapper(cls):
if getattr(cls, 'flag'):
raise Exception("Already expended the context manager")
setattr(cls, 'flag', True)
return f(cls)
return wrapper
class Context:
def __init__(self):
self.flag = False
@control_manager
def __enter__(self):
return self
def __exit__(self, *args):
pass
@control_manager
def enter(self):
return True
def exit(self):
#do something
pass
with Context() as f:
v = f.enter()
追踪(最近一次通话): 文件“”,第2行,in 文件“”,第4行,在包装器中 例外:已经花费了上下文管理器
然而,它将适用于第二次测试:
c = Context()
v = c.enter()
c.exit()
但是,如果您想将enter
用作课程中上下文管理器的主要块,则可以将其视为classmethod
contextlib.contextmanager
:
import contextlib
class Control:
flag = False
def __init__(self):
pass
def __enter__(self):
with Control.enter() as f:
v = f
return self
def __exit__(self, *args):
pass
@classmethod
@control_manager
@contextlib.contextmanager
def enter(cls):
yield True
with Control() as f:
pass
#runs without exception
with Control.enter() as t:
pass
追踪(最近一次通话): 文件“”,第1行,in 文件“”,第4行,在包装器中 例外:已经花费了上下文管理器
答案 1 :(得分:1)
与@Ajax1234 相比,我更喜欢@ehacinom 的解决方案,因为 ehacinom 的解决方案在其自己的类中保留了对象完整性的责任。 Ajax1234 的解决方案将这个责任交给另一个类,这通常被认为是不好的做法。
但我会省略额外的 enter
和 exit
方法。这些方法可能只会诱使您在 with
块之外使用对象,这不是一件好事。如果您真的觉得需要在 with
块之外使用此对象,您可能需要重新考虑您的设计。
ehacinom 也忘记了 exit
方法的参数。所以这就是我实现这个类的方式:
class Context:
def __init__(self):
self.__running= False
def __enter__(self):
if not self.__running:
logger.info("Entering Context object")
# initialize things here
self.__running= True
else:
logger.info("Already entered Context object")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if self.__running:
logger.info("Exiting Context object")
# terminate things here
self.__running= False
else:
logger.info("Already exited Context object")