2015-06-05 2 views
7

Gibt es in Python eine Möglichkeit, eine Klasse zu schreiben, die fehlerhaft ist, wenn sie nicht mit einer with-Anweisung verwendet wird?Schreiben einer Python-Klasse, die nur als Kontextmanager verwendet werden kann

# Okay: 
with Foo() as f1: 
    f1.func1() 
    f1.func2() 

# Not okay: 
f2 = Foo() 
f2.func1() 

ich es manuell tun können: haben __enter__ ein Flag gesetzt und haben jede andere Methode Prüfung für diese Flagge. Aber gibt es einen schöneren Weg, es zu tun?

Hier Code für die nicht-so-da Art und Weise:

class Foo(object): 
    def __init__(self): 
     self._entered = False 

    def __enter__(self): 
     self._entered = True 
     return self 

    def _verify_entered(self): 
     if not self._entered: 
      raise Exception("Didn't get call to __enter__") 

    def __exit__(self, typ, val, traceback): 
     self._verify_entered() 
     print("In __exit__") 

    def func1(self): 
     self._verify_entered() 
     # do stuff ... 

    def func2(self): 
     self._verify_entered() 
     # do other stuff 
+0

Benötigen Sie 'foo()', um den Fehler zu verursachen, oder wäre 'f2.func1()' OK? – Blckknght

+0

@Blckknght, mit Foo() Ursache der Fehler wäre nur mäßig vorzuziehen zu haben f2.func1() do it – kuzzooroo

+2

Sie könnten einen Dekorateur oder eine Metaklasse verwenden, um die Prüfungen automatisch statt manuell anzuwenden. – agf

Antwort

3

Technisch denke ich, dass Agf es richtig in dem Sinne hat, dass Sie eine Metaklasse verwenden könnten, um das Zeug zu automatisieren. Wenn ich jedoch die grundlegende Motivation dahinter verstehe, würde ich einen anderen Weg vorschlagen.

Angenommen, Sie haben eine Payload Klasse, die Sie über einen Kontextmanager schützen möchten. In diesem Fall erstellen Sie einfach einen Kontext-Manager, der es zurückgibt:

# This should go in a private module. 
class Payload(object): 
    def __init__(self): 
     print 'payload ctor' 

# This should go in the public interface. 
class Context(object): 
    def __init__(self): 
     # Set up here the parameters. 
     pass 

    def __enter__(self): 
     # Build & return a Payload object 
     return Payload() 

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

with Context() as f: 
    # f here is a Payload object. 

Wenn Sie Payload in einem privaten Modul verstecken, du bist gut.

2

können Sie haben Ihre __enter__ Methode ein anderes Objekt als self zurück, wenn Sie Methoden auf die rufen Sie nicht ein Benutzer möchte in der Lage Kontextmanager-Objekt selbst.

class Foo(object): 
    def __enter__(self): 
     print("In __enter__") 
     return Bar() 

    def __exit__(self, typ, val, traceback): 
     print("In __exit__") 

class Bar(object): 
    def func1(self): 
     print("In func1") 

    def func2(self): 
     print("In func2") 

Sie können natürlich haben die Foo und Bar Klassen mehr zusammengebunden, als ich für dieses Beispiel haben. Zum Beispiel könnte sich die Foo Klasse an den Bar Konstruktor in __enter__ übergeben.

Aufruf Foo().func1() funktioniert nicht aus dem offensichtlichen Grund, dass Foo keine solche Methode hat. Wenn Sie möchten, dass Bar für den Benutzer nicht sichtbar ist, könnten Sie seinem Namen einen Unterstrich vorangestehen (andeutet, dass es intern ist) oder sogar verschachteln innerhalb der Foo Klasse (oder sogar die __enter__ Methode, wenn Sie wirklich extrem sein wollen).

+0

:-) Ich habe anscheinend genau dieselbe Antwort gefunden wie Sie. –

Verwandte Themen