Skip to main content
added 1548 characters in body
Source Link
user202729
  • 4.2k
  • 3
  • 31
  • 48

You can also use pop_all --- the core idea is that

pop_all allows you to break up a with block.

This is exactly what you need to do in order to break the with block into the __enter__ method and the __exit__ method.

Like this: the code block

with ExitStack() as stack: stack.enter_context(A(0)) stack.enter_context(A(0)) BODY 

can be broken down such that BODY no longer remains in the with block as follows:

with ExitStack() as stack: stack.enter_context(A(0)) stack.enter_context(A(0)) stack2 = stack.pop_all() # at this point stack.close() is called, but it does not call __exit__ on any A object. BODY stack2.__exit__(None, None, None) 

Of course, you still need to remember to handle the case BODY raises an error, so:

with ExitStack() as stack: stack.enter_context(A(0)) stack.enter_context(A(0)) stack2 = stack.pop_all() with stack2: BODY 

So, as a class:

class Multiopen: def __enter__(self): with ExitStack() as stack1: stack1.enter_context(...) stack1.enter_context(...) self._stack2=stack1.pop_all() def __exit__(self, *args): self._stack.close() 

If any of the __enter__ raises an error, stack1 will unwind them. If none of them raises an error, self._stack2 will unwind them instead.

If you want to implement it yourself

The complexity is not just with ExitStack, the complexityit is also with the with statement itself.

The complexity is not with ExitStack, the complexity is with the with statement itself.

You can also use pop_all --- the core idea is that

pop_all allows you to break up a with block.

This is exactly what you need to do in order to break the with block into the __enter__ method and the __exit__ method.

Like this: the code block

with ExitStack() as stack: stack.enter_context(A(0)) stack.enter_context(A(0)) BODY 

can be broken down such that BODY no longer remains in the with block as follows:

with ExitStack() as stack: stack.enter_context(A(0)) stack.enter_context(A(0)) stack2 = stack.pop_all() # at this point stack.close() is called, but it does not call __exit__ on any A object. BODY stack2.__exit__(None, None, None) 

Of course, you still need to remember to handle the case BODY raises an error, so:

with ExitStack() as stack: stack.enter_context(A(0)) stack.enter_context(A(0)) stack2 = stack.pop_all() with stack2: BODY 

So, as a class:

class Multiopen: def __enter__(self): with ExitStack() as stack1: stack1.enter_context(...) stack1.enter_context(...) self._stack2=stack1.pop_all() def __exit__(self, *args): self._stack.close() 

If any of the __enter__ raises an error, stack1 will unwind them. If none of them raises an error, self._stack2 will unwind them instead.

If you want to implement it yourself

The complexity is not just with ExitStack, it is also with the with statement itself.

added 80 characters in body
Source Link
user202729
  • 4.2k
  • 3
  • 31
  • 48
from contextlib import contextmanager class Foo: def __init__(self, i, o): self.i = i self.o = o @contextmanager def multiopen(i, o): with stack.enter_context(open(i)) as i, stack.enter_context(open(o)) as o: yield Foo(i, o) 
@contextmanager def _multiopen(i, o): with stack.enter_context(open(i)) as i, stack.enter_context(open(o)) as o: yield Foo(i, o) class multiopen(ContextDecorator): def __init__(self, i, o): self._multiopen_instance = _multiopen(i, o) def __enter__(self): self._multiopen_instance.__enter__() return self def __exit__(self, exc_type, exc_value, traceback): return self._multiopen_instance.__exit__(exc_type, exc_value, traceback)  def __call__(self): ... 

That is, still make a contextmanager, but wrap over it with a ContextDecorator class.

from contextlib import contextmanager class Foo: def __init__(self, i, o): self.i = i self.o = o @contextmanager def multiopen(i, o): with stack.enter_context(open(i)) as i, stack.enter_context(open(o)) as o: yield Foo(i, o) 
@contextmanager def _multiopen(i, o): with stack.enter_context(open(i)) as i, stack.enter_context(open(o)) as o: yield Foo(i, o) class multiopen: def __init__(self, i, o): self._multiopen_instance = _multiopen(i, o) def __enter__(self): self._multiopen_instance.__enter__() return self def __exit__(self, exc_type, exc_value, traceback): return self._multiopen_instance.__exit__(exc_type, exc_value, traceback)  def __call__(self): ... 

That is, still make a contextmanager, but wrap over it with a class.

from contextlib import contextmanager class Foo: def __init__(self, i, o): self.i = i self.o = o @contextmanager def multiopen(i, o): with open(i) as i, open(o) as o: yield Foo(i, o) 
@contextmanager def _multiopen(i, o): with open(i) as i, open(o) as o: yield Foo(i, o) class multiopen(ContextDecorator): def __init__(self, i, o): self._multiopen_instance = _multiopen(i, o) def __enter__(self): self._multiopen_instance.__enter__() return self def __exit__(self, exc_type, exc_value, traceback): return self._multiopen_instance.__exit__(exc_type, exc_value, traceback) 

That is, still make a contextmanager, but wrap over it with a ContextDecorator class.

Source Link
user202729
  • 4.2k
  • 3
  • 31
  • 48

Simplified solution using contextmanager

Sraw's answer can be simplified as follows:

from contextlib import contextmanager class Foo: def __init__(self, i, o): self.i = i self.o = o @contextmanager def multiopen(i, o): with stack.enter_context(open(i)) as i, stack.enter_context(open(o)) as o: yield Foo(i, o) 

You don't need an explicit ExitStack in this case.

Of course, you can use it as follows:

with multiopen() as foo: print(foo) 

If the number of objects is not fixed, then it's still recommended to use ExitStack as shown in Sraw's answer.

A context manager that is designed to make it easy to programmatically combine other context managers and cleanup functions, especially those that are optional or otherwise driven by input data.

If you need the resulting object to be both a contextmanager and a class

Use case described in https://stackoverflow.com/a/48957756/5267751 .

For example you may want something like the following to work:

@multiopen() def f(): ... with multiopen() as foo: ... 

For this, the object being returned by multiopen() must implement all of __call__, __enter__ and __exit__.

The simplest solution is the following.

@contextmanager def _multiopen(i, o): with stack.enter_context(open(i)) as i, stack.enter_context(open(o)) as o: yield Foo(i, o) class multiopen: def __init__(self, i, o): self._multiopen_instance = _multiopen(i, o) def __enter__(self): self._multiopen_instance.__enter__() return self def __exit__(self, exc_type, exc_value, traceback): return self._multiopen_instance.__exit__(exc_type, exc_value, traceback) def __call__(self): ... 

That is, still make a contextmanager, but wrap over it with a class.

Of course, this may looks ugly (you need the @contextmanager decorator, then immediately throw it away and make a class manually). Actually, it's not that bad --- even the recipe in the documentation https://docs.python.org/3/library/contextlib.html#cleaning-up-in-an-enter-implementation reduces a contextmanager.

If you refuse to use @contextmanager

The complexity is not with ExitStack, the complexity is with the with statement itself.

From the documentation:

The following code:

with EXPRESSION as TARGET: SUITE 

is semantically equivalent to:

manager = (EXPRESSION) enter = type(manager).__enter__ exit = type(manager).__exit__ value = enter(manager) hit_except = False try: TARGET = value SUITE except: hit_except = True if not exit(manager, *sys.exc_info()): raise finally: if not hit_except: exit(manager, None, None, None) 

So, if we want to make a class, of course we cannot use with statement.

I can't figure out how to implement this correctly yet.

  • If the first object's __enter__ raises an exception, then we must not call the first object's __exit__.
  • However, if the second object's __enter__ raises an exception, we must call the first object's __exit__.
  • Furthermore, if the second object's __exit__ raises an exception, we must proceed to continue to call the first object's __exit__.
  • The first object's __exit__ may, in turn, suppress that exception.

(In this context, the open() should be seen as the __enter__. It may be more idiomatic to write a contextmanager that represents a file.)