2012-07-11 10 views
11

Estoy tratando de escribir un administrador de contexto que use otros administradores de contexto, para que los clientes no necesiten saber toda la receta, solo la interfaz que presento. No puedo hacerlo usando @contextmanager - el código después de la llamada yield no se ejecuta si se ve interrumpido por una excepción, entonces necesito usar un administrador basado en clases.Escribiendo un administrador de contexto en Python que usa una declaración with

aquí hay un pequeño script de ejemplo:

from contextlib import contextmanager 
import pprint 

d = {} 

@contextmanager 
def simple(arg, val): 
    print "enter", arg 
    d[arg] = val 
    yield 
    print "exit", arg 
    del d[arg] 

class compl(object): 
    def __init__(self, arg, val): 
     self.arg=arg 
     self.val=val 

    def __enter__(self): 
     with simple("one",1): 
      with simple("two",2): 
       print "enter complex", self.arg 
       d[self.arg] = self.val 

    def __exit__(self,*args): 
     print "exit complex", self.arg 
     del d[self.arg] 

print "before" 
print d 
print "" 

with compl("three",3): 
    print d 
    print "" 

print "after" 
print d 
print "" 

que da salida a esto:

before 
{} 

enter one 
enter two 
enter complex three 
exit two 
exit one 
{'three': 3} 

exit complex three 
after 
{} 

quiero que la producción de este:

before 
{} 

enter one 
enter two 
enter complex three 
{'one': 1, 'three': 3, 'two': 2} 

exit complex three 
exit two 
exit one 
after 
{} 

¿Hay alguna manera de contar una clase administrador de contexto basado en la base para envolver a sí mismo con otros administradores de contexto?

+2

Sería útil especificar la versión de Python. –

+0

Disculpe la pregunta, pero ¿por qué querría hacer esto? Me parece natural que el gestor de contexto basado en clases salga último, una vez que haya limpiado sus dependencias. –

+0

Targeting python 2.7, lo siento –

Respuesta

12
@contextmanager 
def compl(arg, val): 
   with simple("one",1): 
        with simple("two",2): 
      print "enter complex", arg 
      try: 
                d[arg] = val 
       yield 
      finally: 
          del d[arg] 
          print "exit complex", arg 
+3

¿Podría identificar/explicar cuál era el problema en el código de asker para que sea más rápido comprender lo que está pasando? :) – n611x007

+1

@naxa: mira los dos últimos ejemplos de salida en la pregunta. El código en la pregunta produce la primera salida, mi código en la respuesta produce el segundo (el deseable). En pocas palabras: el gestor de contexto más anidado debe salir lo antes posible. – jfs

1

El problema con lo que está haciendo, es que en el uso de with en su llamada __enter__, cuando introduce su gestor de contexto envoltura, ambos entrar y luego salir de los gestores de contexto envueltos. Si desea escribir su propio administrador de contexto que ingrese a los administradores de contexto envueltos cuando ingresa el contenedor, luego los abandona cuando se vaya, tendrá que invocar manualmente las funciones de los administradores de contexto envueltos. Probablemente también tendrás que preocuparte por la seguridad de las excepciones.

2

Usted escribe: "No puedo hacerlo usando @contextmanager: el código después de la llamada de rendimiento no se ejecuta si se ve interrumpido por una excepción". Si tiene código que debe ejecutarse, puede colocarlo en un bloque try/finally.

import contextlib 

@contextlib.contextmanager 
def internal_cm(): 
    try: 
     print "Entering internal_cm" 
     yield None 
     print "Exiting cleanly from internal_cm" 
    finally: 
     print "Finally internal_cm" 

@contextlib.contextmanager 
def external_cm(): 
    with internal_cm() as c: 
     try: 
      print "In external_cm_f" 
      yield [c] 
      print "Exiting cleanly from external_cm_f" 
     finally: 
      print "Finally external_cm_f" 

if "__main__" == __name__: 
    with external_cm() as foo1: 
     print "Location A" 
    print 
    with external_cm() as foo2: 
     print "Location B" 
     raise Exception("Some exception occurs!!") 

Salida:

Entering internal_cm 
In external_cm_f 
Location A 
Exiting cleanly from external_cm_f 
Finally external_cm_f 
Exiting cleanly from internal_cm 
Finally internal_cm 

Entering internal_cm 
In external_cm_f 
Location B 
Finally external_cm_f 
Finally internal_cm 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "C:\Anaconda\lib\site-packages\spyderlib\widgets\externalshell\sitecustomize.py", line 540, in runfile 
    execfile(filename, namespace) 
    File "C:\untitled0.py", line 35, in <module> 
    raise Exception("Some exception occurs!!") 
Exception: Some exception occurs!! 
Cuestiones relacionadas