Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

__init__ vs __enter__ in context managers

As far as I understand, __init__() and __enter__() methods of the context manager are called exactly once each, one after another, leaving no chance for any other code to be executed in between. What is the purpose of separating them into two methods, and what should I put into each?

Edit: sorry, wasn't paying attention to the docs.

Edit 2: actually, the reason I got confused is because I was thinking of @contextmanager decorator. A context manager created using @contextmananger can only be used once (the generator will be exhausted after the first use), so often they are written with the constructor call inside with statement; and if that was the only way to use with statement, my question would have made sense. Of course, in reality, context managers are more general than what @contextmanager can create; in particular context managers can, in general, be reused. I hope I got it right this time?

like image 914
max Avatar asked Sep 21 '16 08:09

max


People also ask

What does __ enter __ do in Python?

__enter__ and [__exit__] both are methods that are invoked on entry to and exit from the body of "the with statement" (PEP 343) and implementation of both is called context manager. the with statement is intend to hiding flow control of try finally clause and make the code inscrutable.

Why you should use context managers in Python?

Context managers allow you to allocate and release resources precisely when you want to. The most widely used example of context managers is the with statement. Suppose you have two related operations which you'd like to execute as a pair, with a block of code in between.

Can we create a function to be used as a context manager?

You can also create custom function-based context managers using the contextlib. contextmanager decorator from the standard library and an appropriately coded generator function.

Can context managers be used outside the with statement?

Yes, the context manager will be available outside the with statement and that is not implementation or version dependent. with statements do not create a new execution scope.


1 Answers

As far as I understand, __init__() and __enter__() methods of the context manager are called exactly once each, one after another, leaving no chance for any other code to be executed in between.

And your understanding is incorrect. __init__ is called when the object is created, __enter__ when it is entered with with statement, and these are 2 quite distinct things. Often it is so that the constructor is directly called in with initialization, with no intervening code, but this doesn't have to be the case.

Consider this example:

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 can be initialized separately and entered in multiple with blocks:

Output:

__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 

Furthermore if __init__ and __enter__ weren't separated, it wouldn't be possible to even use the following:

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

since the initialization (within open) is clearly separate from with entry.


The managers created by contextlib.manager are single-entrant, but they again can be constructed outside the with block. Take the example:

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

you can use this as:

def heading(level=1):     return tag('h{}'.format(level))  my_heading = heading() print('Below be my heading') with my_heading:      print('Here be dragons') 

output:

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

However, if you try to reuse my_heading (and, consequently, tag), you will get

RuntimeError: generator didn't yield 
like image 109