Python中的条件语句

时间:2015-01-06 16:36:42

标签: 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()

为了澄清,一种情况会在with语句中包含一个块,而另一种情况可能是同一个块,但没有被封装(即,好像它没有缩进)

当然,最初的实验会给出缩进错误。

9 个答案:

答案 0 :(得分:49)

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

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()

输入stack的任何内容都会像往常一样在exit语句的末尾自动with。 (如果没有输入任何内容,那不是问题。)在此示例中,get_stuff()返回的内容自动exit

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

答案 1 :(得分:40)

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

class dummy_context_mgr():
    def __enter__(self):
        return None
    def __exit__(self, exc_type, exc_value, traceback):
        return False

或:

import contextlib

@contextlib.contextmanager
def dummy_context_mgr():
    yield None

然后将其用作:

with get_stuff() if needs_with() else dummy_context_mgr() as gs:
   # do stuff involving gs or not

您也可以get_stuff()根据needs_with()返回不同的内容。

(有关您在以后版本中可以执行的操作,请参阅Mike's answerDaniel's answer。)

答案 2 :(得分:8)

实现此目的的第三方选项:
https://pypi.python.org/pypi/conditional

from conditional import conditional

with conditional(needs_with(), get_stuff()):
    # do stuff

答案 3 :(得分:7)

从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几乎只是一个无操作上下文管理器。如果您依赖as之后存在的内容,则可以传递一个将产生的参数:

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

否则,它将仅返回None

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

超级整洁,请在此处查看文档:{​​{3}}

答案 4 :(得分:4)

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

>>> 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

这个解决方案有它的怪癖,我注意到从2.7开始它被弃用了。我编写了自己的上下文管理器来处理多个上下文管理器。到目前为止它对我有用,但我并没有真正考虑边缘条件

class ContextGroup(object):
    """A group of context managers that all exit when the group exits."""

    def __init__(self):
        """Create a context group"""
        self._exits = []

    def add(self, ctx_obj, name=None):
        """Open a context manager on ctx_obj and add to this group. If
        name, the context manager will be available as self.name. name
        will still reference the context object after this context
        closes.
        """
        if name and hasattr(self, name):
            raise AttributeError("ContextGroup already has context %s" % name)
        self._exits.append(ctx_obj.__exit__)
        var = ctx_obj.__enter__()
        if name:
            self.__dict__[name] = var

    def exit_early(self, name):
        """Call __exit__ on named context manager and remove from group"""
        ctx_obj = getattr(self, name)
        delattr(self, name)
        del self._exits[self._exits.index(ctx_obj)]
        ctx_obj.__exit__(None, None, None)

    def __enter__(self):
        return self

    def __exit__(self, _type, value, tb):
        inner_exeptions = []
        for _exit in self._exits:
            try:
                _exit(_type, value, tb )
            except Exception, e:
                inner_exceptions.append(e)
        if inner_exceptions:
            r = RuntimeError("Errors while exiting context: %s" 
                % (','.join(str(e)) for e in inner_exceptions))

    def __setattr__(self, name, val):
        if hasattr(val, '__exit__'):
            self.add(val, name)
        else:
            self.__dict__[name] = val

答案 5 :(得分:2)

很难找到@farsil的漂亮的Python 3.3单行代码,因此这是它自己的答案:

with ExitStack() if not needs_with() else get_stuff() as gs:
     # do stuff

请注意,ExitStack应该排在最前面,否则将评估get_stuff()

答案 6 :(得分:0)

所以我编写了这段代码; 调用方式如下:

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()

属性:

  1. 除非条件为真,否则它不会调用get_stuff()
  2. 如果条件为false,它将提供一个虚拟的contextmanager。 (对于python> = 3.7,可能用contextlib.nullcontext代替)
  3. (可选)您可以在条件为假的情况下发送替代的contextmanager:
    with c_with(needs_with(), lambda: get_stuff(), lambda: dont_get_stuff()) as 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'])

答案 7 :(得分:0)

我发现@Anentropic answer不完整。

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)

conditional的完整用法需要3个条件而不是1个条件,原因是:

  1. NameError: name 'b' is not defined,如果未定义a
  2. 函数foo仍必须返回可输入的对象,否则:AttributeError: 'NoneType' object has no attribute '__enter__'

答案 8 :(得分:0)

import contextlib

my_context = None # your context
my_condition = False # your condition

with my_context if my_condition else contextlib.ExitStack():
    print('hello')