[Python-Dev] PEP 343 - multiple context managers in one statement

Nick Coghlan ncoghlan at gmail.com
Wed Oct 26 00:20:50 CEST 2005


Paul Moore wrote:
> I have a deep suspicion that this has been done to death already, but
> my searching ability isn't up to finding the reference. So I'll simply
> ask the question, and not offer a long discussion:
> 
> Has the option of letting the with statement admit multiple context
> managers been considered (and presumably rejected)?
> 
> I'm thinking of
> 
>     with expr1, expr2, expr3:
>         # whatever

Not rejected - deliberately left as a future option (this is the reason why 
the RHS of an as clause has to be parenthesised if you want tuple unpacking).

> In some ways, this doesn't even need an extension to the PEP - giving
> tuples suitable __enter__ and __exit__ methods would do it. Or, I
> suppose a user-defined manager which combined a list of others:
> 
>     class combining:
>         def __init__(*mgrs):
>             self.mgrs = mgrs
>         def __with__(self):
>             return self
>         def __enter__(self):
>             return tuple(mgr.__enter__() for mgr in self.mgrs)
>         def __exit__(self, type, value, tb):
>             # first in, last out
>             for mgr in reversed(self.mgrs):
>                 mgr.__exit__(type, value, tb)
> 
> Would that be worth using as an example in the PEP?

The issue with that implementation is that the semantics are wrong - it 
doesn't actually mirror *nested* with statements. If one of the later 
__enter__ methods, or one of the first-executed __exit__ methods throws an 
exception, there are a lot of __exit__ methods that get skipped.

Getting it right is more complicated (and this probably still has mistakes):

      class nested(object):
          def __init__(*mgrs):
              self.mgrs = mgrs
              self.entered = None

          def __context__(self):
              return self

          def __enter__(self):
              self.entered = deque()
              vars = []
              try:
                  for mgr in self.mgrs:
                      var = mgr.__enter__()
                      self.entered.push_front(mgr)
                      vars.append(var)
              except:
                  self.__exit__(*sys.exc_info())
                  raise
              return vars

          def __exit__(self, *exc_info):
              # first in, last out
              # Behave like nested with statements
              ex = exc_info
              for mgr in self.entered:
                  try:
                      mgr.__exit__(*ex)
                  except:
                      ex = sys.exc_info()
              if ex is not exc_info:
                  raise ex[0], ex[1], ex[2]

> PS The signature of __with__ in example 4 in the PEP is wrong - it has
> an incorrect "lock" parameter.

Thanks - I'll fix that when I incorporate the resolutions of the open issues 
(which will be post the SVN migration).

Cheers,
Nick.


-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia
---------------------------------------------------------------
             http://boredomandlaziness.blogspot.com


More information about the Python-Dev mailing list