Improvvisare un sostituto immediato per l'istruzione "with" per Python 2.4
-
20-09-2019 - |
Domanda
Potete suggerire un modo per codificare un sostituto drop-in per l'istruzione "with" che funzionerà in Python 2.4?
Sarebbe un trucco, ma mi permetterebbe di portare il mio progetto su Python 2.4 in modo più efficace.
MODIFICARE:Rimosso lo schizzo della metaclasse irrilevante
Soluzione
Basta usare try-fine.
In realtà, questo può essere bello come un esercizio mentale, ma se effettivamente farlo in codice che si cura di te finirà con brutto, difficile da mantenere il codice.
Altri suggerimenti
Penso che potresti (ab)usare i decoratori per farlo.I seguenti lavori, ad esempio:
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,
(Bene, in Python 2.4 gli oggetti file non hanno i metodi __enter__ e __exit__, ma per il resto funziona)
L'idea è che stai sostituendo la riga con in:
with bar() as foo:
do_something_with(foo)
do_something_else_with(foo)
# etc...
con la funzione decorata "dichiarazione" in:
@execute_with_context_manager( bar() )
def dummyname( foo ):
do_something_with(foo)
do_something_else_with(foo)
# etc...
ma ottenendo lo stesso comportamento (il do_something_...codice eseguito).Nota che il decoratore cambia la dichiarazione della funzione in an invocazione immediata il che è più che un po' malvagio.
Dal momento che è necessario uscire dal gestore contesto sia durante gli errori e non gli errori, non penso che sia possibile fare un caso d'uso generico con metaclassi, o di fatto a tutti. Si sta andando ad avere bisogno di try / finally blocchi per questo.
Ma forse è possibile fare qualcosa di diverso nel tuo caso. Dipende da quello che si utilizza il manager contesto per.
Utilizzando __del__
può aiutare in alcuni casi, come risorsa deallocando, ma dal momento che non si può essere sicuri che viene chiamato, può essere utilizzato solo è necessario liberare le risorse che saranno rilasciate quando il programma termina. Anche questo non funziona se si sta gestendo le eccezioni nel metodo __exit__
.
Credo che il metodo più pulito è quello di avvolgere l'intera gestione del contesto in una sorta di contesto chiamata gestione, ed estrarre il blocco di codice in un metodo. Qualcosa di simile (codice non testato, ma soprattutto rubato 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)
Che ne dici di questo?
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
Utilizzo:
@improvize_context_manager(lock)
def null():
do(stuff)
che affianca la parola with
senza as
.
o
@improvize_context_manager(my_lock=lock)
def null(my_lock):
do(stuff_with, my_lock)
che affianca la parola chiave with
con il as
.
Se sei OK con l'utilizzo DEF solo per ottenere un blocco, e decoratori che eseguire immediatamente, è possibile utilizzare la firma funzione per ottenere qualcosa di più naturale per il caso di nome.
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