2014-07-08 9 views
10

Come deve essere gestito un gestore di contesto in un altro gestore di contesto in Python?Gestione di istanze di un gestore di contesto in un altro gestore di contesto

Esempio: si supponga di avere la classe A che funge da gestore contesto e la classe B che funge anche da gestore contesto. Ma le istanze della classe B dovranno istanziare e utilizzare un'istanza della classe A. Ho seguito PEP 343 e questa è la soluzione che ho pensato:

class A(object): 
    def __enter__(self): 
     # Acquire some resources here 
     return self 

    def __exit__(seplf, exception_type, exception, traceback): 
     # Release the resources and clean up 
     pass 


class B(object): 
    def __init__(self): 
     self.a = A() 

    def __enter__(self): 
     # Acquire some resources, but also need to "start" our instance of A 
     self.a.__enter__() 
     return self 

    def __exit__(self, exception_type, exception, traceback): 
     # Release the resources, and make our instance of A clean up as well 
     self.a.__exit__(exception_type, exception, traceback) 

È questo l'approccio corretto? O mi mancano alcuni trucchi?

risposta

2

In alternativa, si potrebbe scrivere il codice in questo modo:

with A() as a: 
    with B(a) as b: 
     # your code here 

Un'altra soluzione che si potrebbe desiderare di provare potrebbe essere questo:

class A: 

    def __init__(self): 
     pass 

    def __enter__(self): 
     return self 

    def __exit__(self, exc_type, exc_val, exc_tb): 
     pass 

class B(A): 

    def __init__(self): 
     super().__init__() 

    def __enter__(self): 
     super().__enter__() 
     return self 

    def __exit__(self, exc_type, exc_val, exc_tb): 
     super().__exit__(exc_type, exc_val, exc_tb) 

Dopo aver considerato la spiegazione della vostra situazione, questo potrebbe essere una soluzione migliore:

class Resource: 

    def __init__(self, dependency=None): 
     self.dependency = dependency 
     # your code here 

    def __enter__(self): 
     if self.dependency: 
      self.dependency.__enter__() 
     # your code here 
     return self 

    def __exit__(self, exc_type, exc_val, exc_tb): 
     # your code here 
     if self.dependency: 
      self.dependency.__exit__(exc_type, exc_val, exc_tb) 

Non sono sicuro se la seguente implementazione è corretto, ma __exit__ deve fare attenzione alle eccezioni. È alquanto difficile per me immaginare come associare ricorsivamente le chiamate mentre si gestiscono correttamente le eccezioni.

class Resource: 

    def __init__(self, dependency=None): 
     self.dependency = dependency 
     self.my_init() 

    def __enter__(self): 
     if self.dependency: 
      self.dependency.__enter__() 
     return self.my_enter() 

    def __exit__(self, exc_type, exc_val, exc_tb): 
     suppress = False 
     try: 
      suppress = self.my_exit(exc_type, exc_val, exc_tb): 
     except: 
      exc_type, exc_val, exc_tb = sys.exc_info() 
     if suppress: 
      exc_type = exc_val = exc_tb = None 
     if self.dependency: 
      suppress = self.dependeny.__exit__(exc_type, exc_val, exc_tb) 
      if not supress: 
       raise exc_val.with_traceback(exc_tb) from None 
     return suppress 

    def my_init(self): 
     pass 

    def my_enter(self): 
     pass 

    def my_exit(self, exc_type, exc_val, exc_tb): 
     pass 
+1

Interessante. Potrebbe funzionare anche Lo svantaggio di questo è che l'utente della nostra classe 'B' dovrà creare l'istanza di' A' e passarla a noi. Inoltre, diventerebbe sempre più complicato se la catena dei requisiti fosse più profonda di un livello. Ma buona idea per casi semplici. Grazie. – Sahand

+0

@NoctisSkytower Il tuo approccio basato sulla classe è praticabile solo se ha davvero senso che B sia una sottoclasse di A. IMO che la relazione non dovrebbe essere creata rigorosamente per renderli più semplici come gestori di contesto, poiché viola il " IS-A "principio di programmazione OO. – dano

+0

Sono d'accordo con @dano. Se logicamente ha senso che 'B' sia una sottoclasse di' A', questa è un'ottima soluzione. Il mio esempio sopra è semplificato. Nel mio caso d'uso effettivo, è la stessa classe che ha riferimenti a istanze di sé in modo ricorsivo (un po 'come una lista concatenata), e quindi tutti devono essere rilasciati in modo ricorsivo. L'idea dell'eredità non funzionerebbe lì. – Sahand

1

Se è possibile utilizzare il @contextlib.contextmanager decoratore tua vita diventa molto più facile:

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", c 
      yield [c] 
      print "Exiting cleanly from external_cm_f", c 
     finally: 
      print "Finally external_cm_f", c 


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