[Python-Dev] PEP 343: Resource Composition and Idempotent __exit__

Nick Coghlan ncoghlan at gmail.com
Sun May 15 13:39:51 CEST 2005


Ka-Ping Yee wrote:
>   - The generator style in PEP 340 is the easiest to compose and
>     reuse, but its implementation is the most complex to understand.

The latest version of my PEP 3XX aims to get (most of) the power of PEP 340, 
with the easy comprehensibility of PEP 310. What magic it requires is almost 
entirely contained in the statement_template decorator.

It can be looked at as PEP 340 without the looping or ability to suppress 
exceptions, or as PEP 343, with PEP 340's style of using generators to write 
templates.

It falls into the category where __enter__ and __exit__ are paired, as it uses 
the same expansion as Shane describes (an exception in __enter__ means that 
__exit__ is never executed).


  > To evaluate these options, we could look at a few scenarios where we're
> trying to write a resource wrapper for some lock objects.  Each lock
> object has two methods, .acquire() and .release().
> 
>     Scenario 1. You have two resource objects and you want to acquire both.
> 
>     Scenario 2. You want a single resource object that acquires two locks.
> 
>     Scenario 3. Your resource object acquires one of two locks depending
>         on some runtime condition.

For your three scenarios, PEP 3XX usage and implementation are as you describe 
for PEP 343. PEP 343 itself doesn't work as you describe, as it still prohibits 
yielding inside a try/finally block (and, by extension, inside a with statement, 
as that is just syntactic sugar for a particular type of try/finally).

Scenario 1 (two locks, handled manually):

PEP 3XX actually recommends supplying __enter__ and __exit__ directly on lock 
objects, so no additional 'resource' wrapper is required:

     with lock1:
         with lock2:
             BLOCK

And the relevant lock methods are:

     def __enter__(self):
         self.acquire()

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

However, if that didn't happen, and an external wrapper was needed, it could be 
optimally implemented as:

     class Resource(object):
         def __init__(self, lock):
             self.lock = lock

         def __enter__(self):
             self.lock.acquire()

         def __exit__(self, *exc):
             self.lock.release()

Or less efficiently as:

     @statement_template
     def Resource(lock):
         lock.acquire()
         try:
             yield
         finally:
             lock.release()

Scenario 2 (two locks, handled by resource):

Used as:

     with DoubleResource(lock1, lock2):
         BLOCK


Implemented as:

     @statement_template
     def DoubleResource(resource1, resource2):
         with resource1:
             with resource2:
                 yield

Scenario 3 (runtime choice of lock):

Used as:

     with ConditionalResource(condition, lock1, lock2):
         BLOCK

Implemented as:

     @statement_template
     def ConditionalResource(condition, resource1, resource2):
         if condition:
             with resource1:
                 yield
         else:
             with resource2:
                 yield

Cheers,
Nick.

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


More information about the Python-Dev mailing list