2016-09-21 15 views
7

Z tego, co rozumiem, __init__() i __enter__() metody menedżera kontekstu są wywoływane dokładnie jeden po drugim, jeden po drugim, nie pozostawiając żadnej szansy na wykonanie innego kodu pomiędzy nimi. Jaki jest cel rozdzielenia ich na dwie metody i co powinienem umieścić w każdym z nich?__init__ vs __enter__ w menedżerach kontekstowych

Edycja: przepraszam, nie zwracałem uwagi na dokumenty.

Edytuj 2: właściwie, powodem, dla którego się pomyliłem, jest to, że myślałem o dekoratorze @contextmanager. Menedżer kontekstu utworzony przy użyciu @contextmananger może być użyty tylko jeden raz (generator zostanie wyczerpany po pierwszym użyciu), więc często są one zapisywane za pomocą wywołania konstruktora wewnątrz instrukcji with; i jeśli byłby to jedyny sposób, aby użyć oświadczenia with, moje pytanie miałoby sens. Oczywiście w rzeczywistości menedżerowie kontekstu są bardziej ogólni niż to, co może stworzyć; w szczególności menedżerowie kontekstowi mogą na ogół zostać ponownie wykorzystani. Mam nadzieję, że tym razem dobrze się stało?

+1

Dezorientujesz * tworzenie * menedżera kontekstów z * wprowadzaniem * kontekstu. Oba są różne i możesz użyć tego samego menedżera kontekstu więcej niż jeden raz. –

Odpowiedz

20

O ile mi zrozumieć, __init__() i __enter__() metody kierownika kontekstowego nazywane są dokładnie raz każdy, jeden po drugim, nie pozostawiając żadnych szans na jakikolwiek inny kod wykonywany pomiędzy.

Twoje zrozumienie jest nieprawidłowe. __init__ jest wywoływany, gdy obiekt jest tworzony, __enter__, gdy jest wprowadzony z instrukcją with i są to 2 całkiem różne rzeczy. Często jest tak, że konstruktor jest wywoływany bezpośrednio w inicjalizacji with, bez interweniującego kodu, ale nie musi tak być.

Rozważmy przykład:

class Foo: 
    def __init__(self): 
     print('__init__ called') 
    def __enter__(self): 
     print('__enter__ called') 
     return self 
    def __exit__(self, *a): 
     print('__exit__ called') 

myobj = Foo() 

print('\nabout to enter with 1') 
with myobj: 
    print('in with 1') 

print('\nabout to enter with 2') 
with myobj: 
    print('in with 2') 

myobj można zainicjować osobno umieszczone w kilku with bloczki

wyjściowa:

__init__ called 

about to enter with 1 
__enter__ called 
in with 1 
__exit__ called 

about to enter with 2 
__enter__ called 
in with 2 
__exit__ called 

Ponadto jeśli __init__ i __enter__ nie oddzielono nie można nawet użyć następującego:

def open_etc_file(name): 
    return open(os.path.join('/etc', name)) 

with open_etc_file('passwd'): 
    ... 

od momentu inicjalizacji (w obrębie open) jest wyraźnie oddzielona od pozycji with.


Zarządzający utworzony contextlib.manager jedno- wklęsła, ale też może być wykonana na zewnątrz bloku with.Weźmy przykład:

from contextlib import contextmanager 

@contextmanager 
def tag(name): 
    print("<%s>" % name) 
    yield 
    print("</%s>" % name) 

można wykorzystać jako:

def heading(level=1): 
    return tag('h{}'.format(level)) 

my_heading = heading() 
print('Below be my heading') 
with my_heading: 
    print('Here be dragons') 

wyjściowa:

Below be my heading 
<h1> 
Here be dragons 
</h1> 

Jeśli jednak spróbować ponownie użyć my_heading (a co za tym idzie, tag), ty otrzyma

RuntimeError: generator didn't yield 
+1

Oh man jakoś wszystkie przykłady, które zapamiętałem, mają wywołanie konstruktora wewnątrz instrukcji 'with' (np.' With Foo() ... '). Teraz wszystko ma sens. Thx – max

+0

Oh czekaj, ale co z '@ contextmanager'? Ponieważ polega on na generatorze, czy nie zużywa go przy pierwszym użyciu, a zatem uniemożliwia wielokrotne używanie obiektu? – max

+0

@max yes, 'contextmanager' jest dla jednorazowych menedżerów kontekstowych. –

0

Odpowiedź Antti Haapalasa jest całkowicie w porządku. Chciałem rozwinąć trochę na używaniu argumentów (jak myClass(* args)), ponieważ to było nieco niejasne do mnie (przyznawana wstecznie zadaję sobie pytanie dlaczego ....)

Używanie argumentów za inicjowanie swoją klasę w with oświadczenie nie jest różni się od korzystania z klasy w zwykły sposób. Połączenia nastąpi w następującej kolejności:

  1. __init__ (podział na klasy)
  2. __enter__ (podaj kontekst)
  3. __exit__ (w kontekście wychodzenia)

Prosty przykład:

class Foo: 
    def __init__(self, i): 
     print('__init__ called: {}'.format(i)) 
     self.i = i 
    def __enter__(self): 
     print('__enter__ called') 
     return self 
    def do_something(self): 
     print('do something with {}'.format(self.i)) 
    def __exit__(self, *a): 
     print('__exit__ called') 

with Foo(42) as bar: 
    bar.do_something() 

Wynik:

__init__ called: 42 
__enter__ called 
    do something with 42 
__exit__ called 

Jeśli chcesz się upewnić, że Twoje połączenia mogą (prawie) być używane tylko w kontekście (np. aby wymusić połączenie z numerem __exit__), patrz stackoverflow post here. W komentarzach znajdziesz także odpowiedź na pytanie, jak używać argumentów nawet wtedy.

Powiązane problemy