Подтвердить что ты не робот

Условное выражение с инструкцией в Python

Есть ли способ начать блок кода с оператором 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()

Чтобы уточнить, один сценарий имел бы блок, заключенный в оболочку в операторе с, тогда как другая возможность была бы тем же самым блоком, но не заключена в оболочку (то есть, как если бы он не был отступом)

Исходные эксперименты, конечно, дают ошибки отступов.

4b9b3361

Ответ 1

Если вы хотите избежать дублирования кода и используете версию Python до 3.7 (когда был представлен 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().

(См. Ответ Майка или Даниэля, что вы можете сделать в более поздних версиях.)

Ответ 2

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 ed в конце инструкции with, как обычно. (Если ничего не введено, это не проблема.) В этом примере все, что возвращается get_stuff(), автоматически exit ed.

Если вам нужно использовать более раннюю версию python, вы можете использовать модуль contextlib2, хотя это не стандартно, Он поддерживает эту и другие функции в более ранних версиях python. Вы можете даже сделать условный импорт, если вам нравится этот подход.

Ответ 3

Сторонний вариант для достижения именно этого:
https://pypi.python.org/pypi/conditional

from conditional import conditional

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

Ответ 4

Начиная с 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 - это просто contextlib.nullcontext менеджер контекста. Вы можете передать его аргумент, что он будет уступать, если вы зависите от чего - то существующего после того, as:

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

В противном случае он просто вернет None:

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

Это супер аккуратно, ознакомьтесь с документами для этого здесь: https://docs.python.org/3/library/contextlib.html#contextlib.nullcontext

Ответ 5

Вы можете использовать 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

Ответ 6

Трудно было найти @farsil изящный однострочный Python 3.3, так что вот он в своем собственном ответе:

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

Обратите внимание, что ExitStack должен стоять первым, иначе get_stuff() будет оценен.

Ответ 7

Итак, я сделал этот код; Он вызывается так:

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. если условие ложно, оно предоставляет фиктивный менеджер контекста. (вероятно, может быть заменено contextlib.nullcontext для python> = 3.7)
  3. При желании вы можете отправить альтернативный контекстный менеджер, если условие ложно:
    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'])