2014-07-08 3 views
10

Wie sollte ein in einem anderen Kontextmanager erstellter Kontextmanager in Python behandelt werden?Behandeln von Instanzen eines Kontextmanagers in einem anderen Kontextmanager

Beispiel: Angenommen, Sie haben die Klasse A, die als Kontextmanager fungiert, und die Klasse B, die auch als Kontextmanager fungiert. Aber Klasse B Instanzen müssen instanziieren und eine Instanz der Klasse A verwenden. Ich bin durch PEP 343 gegangen und das ist die Lösung, an die ich gedacht habe:

Ist das der richtige Ansatz? Oder fehle ich ein paar Gotchas?

Antwort

2

Alternativ können Sie Ihren Code wie so schreiben:

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

Eine andere Lösung, die Sie vielleicht ausprobieren möchten dies sein könnte:

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) 

Nach Prüfung der Erklärung Ihrer Situation, könnte dies sein eine bessere Lösung:

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) 

Ich bin mir nicht sicher, ob die folgende Implementierung ist korrekt, aber __exit__ muss sorgfältig auf Ausnahmen achten. Es ist etwas schwierig für mich, sich vorzustellen, wie man die Aufrufe rekursiv kettet, während Ausnahmen gut behandelt werden.

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

Interessant. Das könnte auch funktionieren. Der Nachteil davon ist, dass der Benutzer unserer Klasse "B" die Instanz von "A" erstellen und an uns weitergeben muss. Außerdem würde es immer komplizierter werden, wenn die Kette der Anforderungen mehr als eine Ebene tief ist. Aber gute Idee für einfache Fälle. Vielen Dank. – Sahand

+0

@NoctisSkytower Ihr klassenbasierter Ansatz ist nur dann praktikabel, wenn es wirklich sinnvoll ist, dass B eine Unterklasse von A. IMO ist. Diese Beziehung sollte nicht strikt erstellt werden, um sie als Kontextmanager einfacher zu verschachteln, da sie gegen die " IS-A "Prinzip der OO-Programmierung. – dano

+0

Ich stimme @dano zu. Wenn es logisch sinnvoll ist, dass "B" eine Unterklasse von "A" ist, ist dies eine sehr gute Lösung. Mein Beispiel oben ist zu stark vereinfacht. In meinem tatsächlichen Anwendungsfall ist es dieselbe Klasse, die rekursiv Referenzen auf Instanzen von sich selbst hat (etwa wie eine verkettete Liste), und daher müssen sie alle rekursiv freigegeben werden. Die Vererbungsidee würde dort nicht funktionieren. – Sahand

1

Wenn Sie den @contextlib.contextmanager Dekorateur verwenden können, um das Leben viel einfacher wird:

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!!") 
Verwandte Themen