防止在可以使用“with”调用的类中调用两次方法

时间:2018-05-02 20:19:04

标签: python function

我正在编写一个可以与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()

2 个答案:

答案 0 :(得分:2)

你很接近,但是,如果在使用装饰器设置标志后已经调用了contextmanager,则可以简单地引发错误。编写以下代码是为了使enter__enter__都可以按原设置返回单独的变量(Trueself),并与上下文管理器的一般概念保持一致:

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 的解决方案将这个责任交给另一个类,这通常被认为是不好的做法。

但我会省略额外的 enterexit 方法。这些方法可能只会诱使您在 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")