[Python-Dev] PEP 343 and __with__

Nick Coghlan ncoghlan at gmail.com
Tue Oct 4 12:21:43 CEST 2005


Jason Orendorff wrote:
> Phillip J. Eby writes:
> 
>>You didn't offer any reasons why this would be useful and/or good.
> 
> 
> It makes it dramatically easier to write Python classes that correctly
> support 'with'.  I don't see any simple way to do this under PEP 343;
> the only sane thing to do is write a separate @contextmanager
> generator, as all of the examples do.

Hmm, it's kind of like the iterable/iterator distinction. Being able to do:

   class Whatever(object):
       def __iter__(self):
           for item in self.stuff:
               yield item

is a very handy way of defining "this is how you iterate over this class". The 
only cost is that actual iterators then need to define an __iter__ method that 
returns 'self' (which isn't much of a cost, and is trivial to do even for 
iterators written in C).

If there was a __with__ slot, then we could consider that as identifying a 
"manageable context", with three methods to identify an actual context manager:
   __with__ that returns self
   __enter__
   __exit__


Then the explanation of what a with statement does would simply look like:

         abc = EXPR.__with__() # This is the only change
         exc = (None, None, None)
         VAR = abc.__enter__()
         try:
             try:
                 BLOCK
             except:
                 exc = sys.exc_info()
                 raise
         finally:
             abc.__exit__(*exc)


And the context management for decimal.Context would look like:
      class Context:
          ...
          @contextmanager
          def __with__(self):
              old = decimal.getcontext()
              new = self.copy() # Make this nesting and thread safe
              decimal.setcontext(new)
              try:
                  yield new
              finally:
                  decimal.setcontext(old)

And for threading.Lock would look like:
      class Lock:
          ...
          def __with__(self):
              return self
          def __enter__(self):
              self.acquire()
              return self
          def __exit__(self):
              self.release()

Also, any class could make an existing independent context manager (such as 
'closing') its native context manager as follows:

      class SomethingCloseable:
          ...
          def __with__(self):
              return closing(self)

> As for the second proposal, I was thinking we'd have one mental model
> for context managers (block template generators), rather than two
> (generators vs. enter/exit methods).  Enter/exit seemed superfluous,
> given the examples in the PEP.

Try to explain the semantics of the with statement without referring to the 
__enter__ and __exit__ methods, and then see if you still think they're 
superfluous ;)

The @contextmanager generator decorator is just syntactic sugar for writing 
duck-typed context managers - the semantics of the with statement itself can 
only be explained in terms of the __enter__ and __exit__ methods. Indeed, 
explaining how the @contextmanager decorator itself works requires recourse to 
the __enter__ and __exit__ methods of the actual context manager object the 
decorator produces.

However, I think the idea of have a distinction between manageable contexts 
and context managers similar to the distinction between iterables and 
iterators is one well worth considering.

Cheers,
Nick.

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


More information about the Python-Dev mailing list