Python中的条件with语句

Python中的条件with语句,python,conditional,indentation,conditional-statements,with-statement,Python,Conditional,Indentation,Conditional Statements,With Statement,有没有一种方法可以用with语句开始一段代码,但要有条件 比如: if needs_with(): with get_stuff() as gs: # do nearly the same large block of stuff, # involving gs or not, depending on needs_with() class dummy_context_mgr(): def __enter__(self): return None de

有没有一种方法可以用with语句开始一段代码,但要有条件

比如:

if needs_with():
    with get_stuff() as gs:

# do nearly the same large block of stuff,
# involving gs or not, depending on needs_with()
class dummy_context_mgr():
    def __enter__(self):
        return None
    def __exit__(self, exc_type, exc_value, traceback):
        return False
为了澄清这一点,一个场景将在with语句中封装一个块,而另一种可能是相同的块,但不封装(即,好像它没有缩进)


初始实验当然会产生缩进错误。

您可以使用
contextlib.nested
将0个或多个上下文管理器放入带有
语句的单个

>>> import contextlib
>>> managers = []
>>> test_me = True
>>> if test_me:
...     managers.append(open('x.txt','w'))
... 
>>> with contextlib.nested(*managers):                                                       
...  pass                                                    
...                                                             
>>> # see if it closed
... managers[0].write('hello')                                                                                                                              
Traceback (most recent call last):                              
  File "<stdin>", line 2, in <module>                                   
ValueError: I/O operation on closed file

如果您希望避免重复代码,并且正在使用3.7(引入
contextlib.nullcontext
时)甚至3.3(引入
contextlib.ExitStack
时)之前的Python版本,您可以执行以下操作:

if needs_with():
    with get_stuff() as gs:

# do nearly the same large block of stuff,
# involving gs or not, depending on needs_with()
class dummy_context_mgr():
    def __enter__(self):
        return None
    def __exit__(self, exc_type, exc_value, traceback):
        return False
或:

然后将其用作:

with get_stuff() if needs_with() else dummy_context_mgr() as gs:
   # do stuff involving gs or not
您也可以使用()使
get\u stuff()
根据
需要返回不同的东西

(请参阅或了解在更高版本中可以做什么。)

针对这种情况引入的Python 3.3。它为您提供了一个“堆栈”,您可以根据需要向其中添加上下文管理器。在您的情况下,您可以这样做:

from contextlib import ExitStack

with ExitStack() as stack:
    if needs_with():
        gs = stack.enter_context(get_stuff())

    # do nearly the same large block of stuff,
    # involving gs or not, depending on needs_with()
输入到
堆栈
中的任何内容都会像往常一样在
语句的末尾自动
退出
ed。(如果没有输入任何内容,这不是问题。)在本例中,由
get_stuff()
返回的任何内容都将被
exit
自动删除


如果您必须使用早期版本的python,则可以使用该模块,尽管这不是标准的。它将此功能和其他功能向后移植到python的早期版本。如果您喜欢这种方法,您甚至可以进行有条件的导入。

第三方选项可以实现这一点:


从Python 3.7开始,您可以使用
contextlib.nullcontext

from contextlib import nullcontext

if needs_with():
    cm = get_stuff()
else:
    cm = nullcontext()

with cm as gs:
    # Do stuff
contextlib.nullcontext
几乎就是一个无操作的上下文管理器。如果依赖于
后面的现有内容,则可以向其传递一个参数,该参数将产生:

>>> with nullcontext(5) as value:
...     print(value)
...
5
否则它只会返回
None

>>> with nullcontext() as value:
...     print(value)
...
None

它非常简洁,请查看这里的文档:

很难找到@farsil漂亮的Python 3.3一行程序,因此这里有它自己的答案:

with ExitStack() if not needs_with() else get_stuff() as gs:
     # do stuff
请注意,ExitStack应该放在第一位,否则将计算
get\u stuff()

所以我做了这个代码; 它是这样调用的:

with c_with(needs_with(), lambda: get_stuff()) as gs:
    ##DOESN't call get_stuff() unless needs_with is called.
    # do nearly the same large block of stuff,
    # involving gs or not, depending on needs_with()
特性:

  • 除非条件为true,否则它不会调用
    get\u stuff()
  • 如果条件为false,则提供一个虚拟contextmanager。(对于python>=3.7,可能会替换为
    contextlib.nullcontext
  • 如果条件为false,您也可以选择发送另一个contextmanager:
    将c_with(needs_with(),lambda:get_stuff(),lambda:dont_get_stuff())作为gs:
  • 希望这能帮助别人

    --代码如下:

    def call_if_lambda(f):
        """
        Calls f if f is a lambda function.
        From https://stackoverflow.com/a/3655857/997253
        """
        LMBD = lambda:0
        islambda=isinstance(f, type(LMBD)) and f.__name__ == LMBD.__name__
        return f() if islambda else f
    import types
    class _DummyClass(object):
        """
        A class that doesn't do anything when methods are called, items are set and get etc.
        I suspect this does not cover _all_ cases, but many.
        """
        def _returnself(self, *args, **kwargs):
            return self
        __getattr__=__enter__=__exit__=__call__=__getitem__=_returnself
        def __str__(self):
            return ""
        __repr__=__str__
        def __setitem__(*args,**kwargs):
            pass
        def __setattr__(*args,**kwargs):
            pass
    
    class c_with(object):
        """
        Wrap another context manager and enter it only if condition is true.
        Parameters
        ----------
        condition:  bool
            Condition to enter contextmanager or possibly else_contextmanager
        contextmanager: contextmanager, lambda or None
            Contextmanager for entering if condition is true. A lambda function
            can be given, which will not be called unless entering the contextmanager.
        else_contextmanager: contextmanager, lambda or None
            Contextmanager for entering if condition is true. A lambda function
            can be given, which will not be called unless entering the contextmanager.
            If None is given, then a dummy contextmanager is returned.
        """
        def __init__(self, condition, contextmanager, else_contextmanager=None):
            self.condition = condition
            self.contextmanager = contextmanager
            self.else_contextmanager = _DummyClass() if else_contextmanager is None else else_contextmanager
        def __enter__(self):
            if self.condition:
                self.contextmanager=call_if_lambda(self.contextmanager)
                return self.contextmanager.__enter__()
            elif self.else_contextmanager is not None:
                self.else_contextmanager=call_if_lambda(self.else_contextmanager)
                return self.else_contextmanager.__enter__()
        def __exit__(self, *args):
            if self.condition:
                return self.contextmanager.__exit__(*args)
            elif self.else_contextmanager is not None:
                self.else_contextmanager.__exit__(*args)
    
    #### EXAMPLE BELOW ####
    
    from contextlib import contextmanager
    
    def needs_with():
        return False
    
    @contextmanager
    def get_stuff():
        yield {"hello":"world"}
    
    with c_with(needs_with(), lambda: get_stuff()) as gs:
        ## DOESN't call get_stuff() unless needs_with() returns True.
        # do nearly the same large block of stuff,
        # involving gs or not, depending on needs_with()
        print("Hello",gs['hello'])
    

    我发现@Anentropic是不完整的

    from conditional import conditional
    
    a = 1 # can be None
    
    if not a is None:
      b = 1
    
    class WithNone:
      def __enter__(self):
        return self
      def __exit__(self, type, value, tb):
        pass
    
    def foo(x):
      print(x)
      return WithNone()
    
    with conditional(not a is None, foo(b) if not a is None else None):
      print(123)
    
    完整的
    有条件的
    使用需要3个条件,而不是1个条件,因为:

  • name错误:如果未定义名称“b”,则名称“b”未定义
  • 函数
    foo
    仍然必须返回可输入的对象,否则:
    AttributeError:'NoneType'对象没有属性'\uuuuuu enter\uuu'

  • 为with的主体编写函数?正如我在回答中提到的,python 3.3添加了
    contextlib.ExitStack
    ,这似乎与
    ContextGroup
    的功能非常相似。我会说,我有点惊讶它没有被后端口,但是如果你愿意要求python>=3.3,那可能是一个很好的健壮的替代方案。
    contextlib2
    是一个pypi包,它已经将
    ExitStack
    后端口到python 2。这个上下文管理器应该在标准python库imho中。谢谢你。用你应该知道的名字怎么样。。。别这样。然后,像这样的语句会读为带有get_stuff()的
    ,如果get_stuff()或者不作为gs:
    ?@RiazRizvi我个人不会这样命名;我使用的是问题中的名称。@jjmontes(新的python 3.3)可以用作虚拟上下文管理器。它是否支持
    as…
    子句(位于
    with
    语句末尾)?查看源代码。。。是的<代码>带有条件(需要使用,获取内容())作为内容:
    将为您提供对
    获取内容()
    上下文管理器的引用(如果且仅当条件满足,否则您将获取
    )我发现您的答案不完整:+1,这应该是所选答案。正如所指出的,它是为了解决这类问题。此外,它还可以用作一个漂亮的单行程序:
    如果需要,则使用get_stuff(),否则将作为gs退出stuck()。您仍然只将两个参数传递给
    conditional
    ,这并没有给我的原始答案添加任何内容。。。看来你的问题就在于正确准备这两个论点needed@Anentropic我的观点是,仅仅让
    有条件的
    发挥作用是不够的,另外还需要一些变通办法来让它发挥作用,这些办法不像你的答案那么简单,而且会降低整个问题的适用性。
    请注意,ExitStack应该放在第一位,否则将对get_stuff()进行求值
    -不,这不会引起问题,但在输入with语句之前调用get_stuff()是否总是安全的?将open(file)作为fh
    是否等同于将f=open(file)
    后跟将f作为fh的
    ?这取决于上下文管理器的操作。大多数上下文管理器不应该在其
    \uuuuu init\uuuuuuu
    方法中执行操作,而只应该在其
    \uuuuuuuuu enter\uuuuuuuu
    方法(或
    \uuuuuu aenter\uuuuuuuuuu
    )中执行操作,该方法在
    with
    语句中使用时调用。不幸的是,答案是“视情况而定”。如果您对此感到担心,可以将函数分配给
    cm
    ,而无需调用它们(使用
    functools.parti)
    
    from conditional import conditional
    
    a = 1 # can be None
    
    if not a is None:
      b = 1
    
    class WithNone:
      def __enter__(self):
        return self
      def __exit__(self, type, value, tb):
        pass
    
    def foo(x):
      print(x)
      return WithNone()
    
    with conditional(not a is None, foo(b) if not a is None else None):
      print(123)