2015-06-05 18 views
7

Czy istnieje sposób w Pythonie, aby napisać klasę, która będzie zawierać błąd, chyba że jest używana z instrukcją?Pisanie klasy Pythona, która może być używana tylko jako menedżer kontekstu.

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

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

mogę to zrobić ręcznie: nie __enter__ ustawić flagę i mieć każdą inną kontrolę metody dla tej flagi. Ale czy jest lepszy sposób na zrobienie tego?

Oto kod dla nie tak od okazji:

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

Potrzebujesz dla 'foo()', aby spowodować błąd, lub czy 'f2.func1()' będzie w porządku? – Blckknght

+0

@Blckknght, mając Foo() spowodować, że błąd jest tylko skromniej niż mieć f2.func1() zrób to – kuzzooroo

+2

Możesz użyć dekoratora lub metaclass, aby zastosować kontrole automatycznie zamiast ręcznie. – agf

Odpowiedz

3

Technicznie rzecz biorąc, myślę, że agf ma to prawo w tym sensie, że można użyć metaklasu do zautomatyzowania tych rzeczy. Jeśli jednak rozumiem fundamentalną motywację, która się za tym kryje, sugeruję inną drogę.

Załóżmy, że masz klasę Payload, którą chcesz chronić za pomocą menedżera kontekstów. W tym przypadku, po prostu utworzyć menedżera kontekstowe, która zwraca go:

# 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. 

Jeśli ukryć Payload w prywatnym modułu, jesteś dobry.

2

Możesz mieć metoda __enter__ powrócić inny obiekt niż self jeśli nie chcemy, by użytkownik, aby móc wywołać metody na obiekt samego menedżera kontekstu.

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") 

Można oczywiście mieć zajęcia Foo i Bar bardziej ze sobą powiązane niż ja na ten przykład. Na przykład klasa Foo może przejść do konstruktora Bar w __enter__.

Wywołanie Foo().func1() nie zadziała z oczywistego powodu, że Foo nie ma żadnej takiej metody. Jeśli chcesz, aby Bar nie było widoczne dla użytkownika, możesz poprzedzić jego nazwę znakiem podkreślenia (wskazującym, że jest on wewnętrzny) lub nawet zagnieździć go w klasie Foo (lub nawet metodą __enter__, jeśli naprawdę chcesz być ekstremalny).

+0

:-) Wydaje mi się, że wymyśliłem dokładnie taką samą odpowiedź jak ty. –