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:
-
no llama a
get_stuff()
menos que la condición sea verdadera -
si la condición es falsa, proporciona un gestor de contexto ficticio.
(probablemente podría reemplazarse por
contextlib.nullcontext
para python> = 3.7) -
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:
-
NameError: name ''b'' is not defined
en caso de que no se haya definidoa
-
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).
Una opción de terceros para lograr exactamente esto:
https://pypi.python.org/pypi/conditional
>>> with nullcontext() as value:
... print(value)
...
None