没有 Yield 的上下文管理器

Context Manager without Yield

我可以有一个偶尔不产生的上下文管理器,在这种情况下 with 语句中的代码根本不执行吗?

import contextlib

@contextlib.contextmanager
def MayNotYield(to_yield):
  if to_yield:
    yield

with MayNotYield(True):  
  print 'This works.'

with MayNotYield(False):  
  print 'This errors.'

我可以要求用户用 try-catch 包装 with 语句,但这不是首选。我也可以执行以下操作,但它也很难看。

import contextlib

@contextlib.contextmanager
def AlwaysYields(to_yield):
  if to_yield:
    yield 1
  else:
    yield 2

with AlwaysYields(True) as result:
  if result == 1:
    print 'This works.'

不幸的是,the context manager protocol 没有给上下文管理器一个说 "Don't run the with block" 的方法(除了在 __enter__ 中引发异常)。如果您无论如何都在使用上下文管理器,我认为您的第二种方法是最好的方法,它具有 __enter__ return 一个值来指示块是否应该 运行 。如果出于其他原因不需要上下文管理器,则可以只使用简单的 if 语句:

if do_stuff:
    # do the stuff

考虑到在上下文管理器实现中有条件的目标,在可以将 with 块的内容提取到它自己的函数中的情况下,还有另一种可能性。您可以将此可调用项传递给上下文管理器,并让上下文管理器 return 传递可调用项或根据布尔值从上下文管理器中调用一个虚拟的什么都不做的可调用项。 with 块将始终执行,但可能会或可能不会调用操作。

def do_something():
    print("This works!")

@contextlib.contextmanager
def conditional_on(condition, f):
    if condition:
        # Acquire resources here
        pass
    else:
        # Replace the callable with a do-nothing dummy
        f = lambda x: x
    try:
        yield f
    finally:
        if condition:
            # Release resources here
            pass    

with conditional_on(True, do_something) as f:
    f()  # Prints message

with conditional_on(False, do_something) as f:
    f()  # Does nothing

您将需要根据上下文管理器正在管理的资源(如果有)以及可调用对象所需的签名来定制此解决方案。

另一种选择是只使用常规生成器而不是上下文管理器;一个普通的生成器不会有这个限制。但是你必须将它与 "for" 结构一起使用,而不是使用 "with":

def MayNotYield(to_yield):
   if to_yield:
      yield

for _ in MayNotYield(True):
   print('This prints.')

for _ in MayNotYield(False):
   print('This does not.')