Improvando uma substituição de entrega para a declaração "com" para o Python 2.4
-
20-09-2019 - |
Pergunta
Você pode sugerir uma maneira de codificar uma substituição de queda para a declaração "com" que funcionará no Python 2.4?
Seria um hack, mas me permitiria portar meu projeto para o Python 2.4 mais bem.
EDITAR:Removido de esboço de metaclasse irrelevante
Solução
Basta usar o TryFinally.
Realmente, isso pode ser bom como um exercício mental, mas se você realmente o fizer em código, você vai acabar com o código feio e difícil de manter.
Outras dicas
Você poderia (AB) usar decoradores para fazer isso, eu acho. Os seguintes trabalhos, por exemplo:
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,
(Bem, nos objetos do arquivo python 2.4 não possuem métodos __enter__ e __Exit__, mas, caso contrário, funciona)
A ideia é que você está substituindo a linha em:
with bar() as foo:
do_something_with(foo)
do_something_else_with(foo)
# etc...
com a função decorada "Declaração" em:
@execute_with_context_manager( bar() )
def dummyname( foo ):
do_something_with(foo)
do_something_else_with(foo)
# etc...
mas obtendo o mesmo comportamento (o código do_something _... executado). Observe o decorador transforma a declaração de função em um invocação imediata o que é mais do que um pouco mal.
Como você precisa sair do gerenciador de contexto durante erros e não erros, não acho que seja possível fazer uma base de gestão genérica com metaclasses, ou de fato. Você precisará tentar/finalmente blocos para isso.
Mas talvez seja possível fazer outra coisa no seu caso. Isso depende do que você usa o gerenciador de contexto.
Usando __del__
Pode ajudar em alguns casos, como o recurso de negociação, mas como você não pode ter certeza de que ele é chamado, ele só pode ser usado para liberar recursos que serão lançados quando o programa sair. Isso também não funcionará se você estiver lidando com exceções no __exit__
método.
Eu acho que o método mais limpo é envolver todo o gerenciamento de contexto em uma espécie de chamada de gerenciamento de contexto e extrair o bloco de código para um método. Algo assim (código não testado, mas principalmente roubado do 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)
Que tal agora?
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)
Que é paralelo ao with
palavra -chave sem as
.
Ou:
@improvize_context_manager(my_lock=lock)
def null(my_lock):
do(stuff_with, my_lock)
Que é paralelo ao with
palavra -chave com o as
.
Se você está bem em usar o DEF apenas para obter um bloco e os decoradores que executam imediatamente, você pode usar a assinatura da função para obter algo mais natural para o caso nomeado.
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