Improvisando un reemplazo directo para la declaración "con" para Python 2.4
-
20-09-2019 - |
Pregunta
¿Puede sugerir una forma de codificar un reemplazo directo para la declaración "con" que funcione en Python 2.4?
Sería un truco, pero me permitiría migrar mejor mi proyecto a Python 2.4.
EDITAR:Se eliminó el boceto de metaclase irrelevante.
Solución
Sólo tiene que utilizar try-finally.
En realidad, esto puede ser agradable como un ejercicio mental, pero si realmente lo hace en código que se preocupan por el resultado final será con feo, difícil de mantener código.
Otros consejos
Creo que podrías (ab)usar decoradores para hacer esto.Los siguientes trabajos, por ejemplo:
def execute_with_context_manager(man):
def decorator(f):
target = man.__enter__()
exc = True
try:
try:
f(target)
except:
exc = False
if not man.__exit__(*sys.exc_info()):
raise
finally:
if exc:
man.__exit__(None, None, None)
return None
return decorator
@execute_with_context_manager(open("/etc/motd"))
def inside(motd_file):
for line in motd_file:
print line,
(Bueno, en Python 2.4 los objetos de archivo no tienen métodos __enter__ y __exit__, pero por lo demás funciona)
La idea es reemplazar la línea with en:
with bar() as foo:
do_something_with(foo)
do_something_else_with(foo)
# etc...
con la función decorada "declaración" en:
@execute_with_context_manager( bar() )
def dummyname( foo ):
do_something_with(foo)
do_something_else_with(foo)
# etc...
pero obteniendo el mismo comportamiento (el hacer_algo_...código ejecutado).Tenga en cuenta que el decorador cambia la declaración de función a una invocación inmediata lo cual es más que un poco de maldad.
Ya que se necesita para salir del gestor de contexto, tanto durante los errores y los errores no, no creo que sea posible hacer un caso de uso genérico con metaclases, o de hecho en absoluto. Usted va a necesitar try / finally bloques para eso.
Pero tal vez es posible hacer otra cosa en su caso. Eso depende de lo que se utiliza el gestor de contexto para.
El uso de __del__
puede ayudar en algunos casos, como recurso cancelar la asignación, pero ya no se puede estar seguro de que se llama, sólo se puede utilizar de lo que necesita para liberar recursos que se liberan cuando el programa termina. Eso también no funcionará si está manejando excepciones en el método __exit__
.
Creo que el método más limpio es envolver toda la gestión de contexto en una especie de llamada de gestión de contexto, y extraer el bloque de código en un método. Algo como esto (código no probado, pero robado en su mayoría de PEP 343):
def call_as_context_manager(mgr, function):
exit = mgr.__exit__
value = mgr.__enter__()
exc = True
try:
try:
function(value)
except:
exc = False
if not exit(*sys.exc_info()):
raise
finally:
if exc:
exit(None, None, None)
¿Qué tal esto?
def improvize_context_manager(*args, **kwargs):
assert (len(args) + len(kwargs)) == 1
if args:
context_manager = args[0]
as_ = None
else: # It's in kwargs
(as_, context_manager) = kwargs.items()[0]
def decorator(f):
exit_ = context_manager.__exit__ # Not calling it yet
enter_ = context_manager.__enter__()
exc = True
try:
try:
if as_:
f(*{as_: enter_})
else:
f()
except:
exc = False
if not exit_(*sys.exc_info()):
raise
finally:
if exc:
exit_(None, None, None)
return None
return decorator
Uso:
@improvize_context_manager(lock)
def null():
do(stuff)
¿Qué es paralela a la palabra clave with
sin as
.
O:
@improvize_context_manager(my_lock=lock)
def null(my_lock):
do(stuff_with, my_lock)
¿Qué es paralela a la palabra clave with
con el as
.
Si estás bien con el uso de DEF sólo para obtener un bloque, y decoradores que se ejecutan inmediatamente, podría utilizar la firma de la función de conseguir algo más natural para el caso mencionado.
import sys def with(func): def decorated(body = func): contexts = body.func_defaults try: exc = None, None, None try: for context in contexts: context.__enter__() body() except: exc = sys.exc_info() raise finally: for context in reversed(contexts): context.__exit__(*exc) decorated() class Context(object): def __enter__(self): print "Enter %s" % self def __exit__(self, *args): print "Exit %s(%s)" % (self, args) x = Context() @with def _(it = x): print "Body %s" % it @with def _(it = x): print "Body before %s" % it raise "Nothing" print "Body after %s" % it