[Python-Dev] PEP 343 - Abstract Block Redux

Nick Coghlan ncoghlan at gmail.com
Sat May 14 11:37:36 CEST 2005


Guido van Rossum wrote:
> I've written up the specs for my "PEP 340 redux" proposal as a
> separate PEP, PEP 343.
> 
> http://python.org/peps/pep-0343.html
> 
> Those who have been following the thread "Merging PEP 310 and PEP
> 340-redux?" will recognize my proposal in that thread, which received
> mostly positive responses there.
> 
> Please review and ask for clarifications of anything that's unclear.

On the keyword front, the two keyword choices affect the naming conventions of 
templates differently, and I think need to be considered in that light.

The naming convention for 'do' is shown in the current PEP 343. The issue I've 
noticed with it is that *functions* read well, but methods don't because things 
get out of sequence. That is, "do locking(the_lock)" reads well, but "do 
the_lock.locking()" does not.

Whereas, using 'with', it can be written either way, and still read reasonably 
well ("with locked(the_lock)", "with the_lock.locked()").

The 'with' keyword also reads better if objects natively support use in 'with' 
blocks ("with the_lock", "with the_file").

Guido's concern regarding file objects being reused inappropriately can be dealt 
with in the file __enter__ method:

   def __enter__(self):
       if self.closed:
           raise RuntimeError, "Cannot reopen closed file handle"

Template generators have the exact same problem with reusability - the solution 
used there is raising a RuntimeError when __enter__() is called inappropriately. 
This would make sense as a standard idiom - if a statement template can't be 
reused, attempting to do so should trigger a RuntimeError the second time 
__enter__() is invoked.

For files, it may then become the common practice to keep pathnames around, 
rather than open file handles. When you actually needed access to the file, the 
existing "open" builtin would suffice:

   with open(filename, "rb") as f:
       for line in f:
           print line

I've written out the PEP 343 examples below, assuming types acquire native with 
statement support (including Decimal contexts - I also give PEP 343 style code 
for Decimal contexts).

PEP343 examples: 'with' keyword, native support in objects

    1. A template for ensuring that a lock, acquired at the start of a
        block, is released when the block is left:

         # New methods on lock objects
             def __enter__(self):
                 self.acquire()

             def __exit__(self, *args):
                 self.release()

        Used as follows:

         with myLock:
             # Code here executes with myLock held.  The lock is
             # guaranteed to be released when the block is left (even
             # if via return or by an uncaught exception).

     2. A template for opening a file that ensures the file is closed
        when the block is left:

         # New methods on file objects
             def __enter__(self):
                 if self.closed:
                     raise RuntimeError, "Cannot reopen closed file handle"

             def __exit__(self, *args):
                 self.close()

        Used as follows:

         with open("/etc/passwd") as f:
             for line in f:
                 print line.rstrip()

     3. A template for committing or rolling back a database
        transaction; this is written as a class rather than as a
        decorator since it requires access to the exception information:

         class transaction:
             def __init__(self, db):
                 self.db = db
             def __enter__(self):
                 self.db.begin()
             def __exit__(self, *args):
                 if args and args[0] is not None:
                     self.db.rollback()
                 else:
                     self.db.commit()

       Used as follows:

         with transaction(db):
             # Exceptions in this code cause a rollback

     5. Redirect stdout temporarily:

         @with_template
         def redirected_stdout(new_stdout):
             save_stdout = sys.stdout
             sys.stdout = new_stdout
             yield None
             sys.stdout = save_stdout

        Used as follows:

         with open(filename, "w") as f:
             with redirected_stdout(f):
                 print "Hello world"

        This isn't thread-safe, of course, but neither is doing this
        same dance manually.  In a single-threaded program (e.g., a
        script) it is a totally fine way of doing things.

     6. A variant on opening() that also returns an error condition:

         @with_template
         def open_w_error(filename, mode="r"):
             try:
                 f = open(filename, mode)
             except IOError, err:
                 yield None, err
             else:
                 yield f, None
                 f.close()

        Used as follows:

         with open_w_error("/etc/passwd", "a") as f, err:
             if err:
                 print "IOError:", err
             else:
                 f.write("guido::0:0::/:/bin/sh\n")

     7. Another useful example would be an operation that blocks
        signals.  The use could be like this:

         from signal import blocked_signals

         with blocked_signals():
             # code executed without worrying about signals

        An optional argument might be a list of signals to be blocked;
        by default all signals are blocked.  The implementation is left
        as an exercise to the reader.

     8. Another use for this feature is the Decimal context.

         # New methods on decimal Context objects
             def __enter__(self):
                 self._old_context = getcontext()
                 setcontext(self)

             def __exit__(self, *args):
                 setcontext(self._old_context)

        Used as follows:

         with decimal.Context(precision=28):
             # Code here executes with the given context
             # The context always reverts after this statement

For comparison, the equivalent PEP 343 code is:

     @do_template
     def with_context(context):
         old_context = getcontext()
         setcontext(context)
         yield None
         setcontext(old_context)

    Used as:

         do decimal.with_context(decimal.Context(precision=28)):
             # Code here executes with the given context
             # The context always reverts after this statement

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


More information about the Python-Dev mailing list