with statement español different python conditional indentation conditional-statements with-statement

statement - Condicional con declaración en Python



with python español (8)

¿Hay alguna manera de comenzar un bloque de código con una instrucción with, pero condicionalmente?

Algo como:

if needs_with(): with get_stuff() as gs: # do nearly the same large block of stuff, # involving gs or not, depending on needs_with()

Para aclarar, un escenario tendría un bloque encerrado en la declaración with, mientras que otra posibilidad sería el mismo bloque, pero no encerrado (es decir, como si no estuviera sangrado)

Los experimentos iniciales, por supuesto, dan errores de sangría.


A partir de Python 3.7 puede usar contextlib.nullcontext :

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

contextlib.nullcontext es prácticamente un administrador de contexto no contextlib.nullcontext . Puede pasarle un argumento de que rendirá, si depende de algo existente después de as :

from contextlib import nullcontext if needs_with(): cm = get_stuff() else: cm = nullcontext() with cm as gs: # Do stuff

De lo contrario, solo devolverá None :

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

Es súper ordenado, mira los documentos aquí: https://docs.python.org/3/library/contextlib.html#contextlib.nullcontext


Entonces hice este código; Se invoca así:

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

Propiedades:

  1. no llama a get_stuff() menos que la condición sea verdadera
  2. si la condición es falsa, proporciona un gestor de contexto ficticio. (probablemente podría reemplazarse por contextlib.nullcontext para python> = 3.7)
  3. Opcionalmente, puede enviar un administrador de contexto alternativo en caso de que la condición sea falsa:
    with c_with(needs_with(), lambda: get_stuff(), lambda: dont_get_stuff()) as gs:

¡Espero que esto ayude a alguien!

- Aquí está el código:

def call_if_lambda(f): """ Calls f if f is a lambda function. From https://.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''])


Fue difícil encontrar el ingenioso Python 3.3 de una sola línea de @ farsil, así que aquí está en su propia respuesta:

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

Tenga en cuenta que ExitStack debería ser lo primero, de lo contrario se get_stuff() .


He encontrado que la respuesta @Anentropic está incompleta.

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)

El uso conditional completo requiere 3 condiciones en lugar de 1 debido a:

  1. NameError: name ''b'' is not defined en caso de que no se haya definido a
  2. la función foo aún debe devolver el objeto entrable, de lo contrario: AttributeError: ''NoneType'' object has no attribute ''__enter__''

Puede usar contextlib.nested para poner 0 o más gestores de contexto en un solo enunciado.

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

Esta solución tiene sus peculiaridades y acabo de notar que a partir de 2.7 ha quedado en desuso. Escribí mi propio administrador de contexto para manejar malabarismos con múltiples administradores de contexto. Me ha funcionado hasta ahora, pero realmente no he considerado las condiciones de borde

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


Python 3.3 introdujo contextlib.ExitStack para este tipo de situación. Le proporciona una "pila", a la que agrega administradores de contexto según sea necesario. En su caso, haría esto:

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

Todo lo que se ingresa a la stack se exit automáticamente al final de la instrucción with como de costumbre. (Si no se ingresa nada, eso no es un problema). En este ejemplo, lo que devuelve get_stuff() se get_stuff() automáticamente.

Si tiene que usar una versión anterior de python, es posible que pueda usar el módulo contextlib2 , aunque esto no es estándar. Es compatible con esta y otras características a versiones anteriores de python. Incluso podría hacer una importación condicional, si le gusta este enfoque.


Si desea evitar la duplicación de código y está utilizando una versión de Python anterior a 3.7 (cuando se introdujo contextlib.nullcontext ) o incluso 3.3 (cuando se introdujo contextlib.ExitStack ), puede hacer algo como:

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

o:

import contextlib @contextlib.contextmanager def dummy_context_mgr(): yield None

y luego úsalo como:

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

Alternativamente, podría hacer que get_stuff() devuelva cosas diferentes en función de needs_with() .

(Vea la respuesta de Mike o la respuesta de Daniel para saber qué puede hacer en versiones posteriores).