[Python-Dev] PEP 377 - allow __enter__() methods to skip the statement body

Nick Coghlan ncoghlan at gmail.com
Sun Mar 15 21:28:37 CET 2009


glyph at divmod.com wrote:
> 
> On 12:56 pm, ncoghlan at gmail.com wrote:
>> PEP 377 is a proposal to allow context manager __enter__() methods to
>> skip the body of the with statement by raising a specific (new) flow
>> control exception.
>>
>> Since there is a working reference implementation now, I thought it was
>> time to open it up for broader discussion.
> 
> Why not allow a context manager to implement some other method, for the
> sake of argument let's say "__start__", which was invoked with a
> callable object and could choose to evaluate or not evaluate the
> statement body by simply not calling that object (or perhaps iterable,
> in the case of a generator)?

So the with statement would in effect create a separate code object for
the statement body that still shared the scope of the containing
function, and then pass a zero-argument callable in to the new method to
allow it to execute that code?

There are some practical hurdles to that idea (specifically, creating a
callable which uses its parent's namespace rather than having its own),
but the basic concept seems sound.

Rough spec for the concept:

Implementing __enter__/__exit__ on a CM would work as per PEP 343.

Implementing __with__ instead would give the CM complete control over
whether or not to execute the block.

The implementation of contextlib.GeneratorContextManager would then
change so that instead of providing __enter__/__exit__ as it does now it
would instead provide __with__ as follows:

  def __with__(self, exec_block):
    try:
      return self.gen.next()
    except StopIteration:
      pass
    else:
      try:
        exec_block()
      except:
        exc_type, value, traceback = sys.exc_info()
        try:
          self.gen.throw(type, value, traceback)
          raise RuntimeError("generator didn't stop after throw()")
        except StopIteration, exc:
          # Suppress the exception *unless* it's the same exception that
          # was passed to throw().  This prevents a StopIteration
          # raised inside the "with" statement from being suppressed
          return exc is not value
        except:
          # only re-raise if it's *not* the exception that was
          # passed to throw(), because __exit__() must not raise
          # an exception unless __exit__() itself failed.  But throw()
          # has to raise the exception to signal propagation, so this
          # fixes the impedance mismatch between the throw() protocol
          # and the __exit__() protocol.
          if sys.exc_info()[1] is not value:
            raise
      else:
        try:
          self.gen.next()
        except StopIteration:
          return
        else:
          raise RuntimeError("generator didn't stop")

More radical in some ways that what I was suggesting, but also cleaner
and more powerful.

Cheers,
Nick.

-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia
---------------------------------------------------------------


More information about the Python-Dev mailing list