没有 Yield 的上下文管理器

问题描述 投票:0回答:3

我可以有一个偶尔不产生结果的上下文管理器吗?在这种情况下,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.'
python contextmanager
3个回答
8
投票

不幸的是,上下文管理器协议没有给上下文管理器一种方法来说“不要运行

with
块”(除了在
__enter__
中引发异常)。如果您无论如何都使用上下文管理器,我认为您的第二种方法是最好的方法,其中
__enter__
返回一个值来指示该块是否应该运行。如果由于其他原因不需要上下文管理器,您可以使用简单的
if
语句:

if do_stuff:
    # do the stuff

7
投票

另一种选择是只使用常规生成器而不是上下文管理器;普通的生成器不会有这个限制。 但你必须将它与“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.')

4
投票

考虑到在上下文管理器实现中包含条件的目标,在可以将 with 块的内容提取到其自己的函数中的情况下,还有另一种可能性。您可以将此可调用对象传递给上下文管理器,并让上下文管理器根据布尔值从上下文管理器返回传递的可调用对象或虚拟的无操作可调用对象。 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

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

© www.soinside.com 2019 - 2024. All rights reserved.