[Python-Dev] PEP 340: Deterministic Finalisation (new PEP draft, either a competitor or update to PEP 340)

Nick Coghlan ncoghlan at gmail.com
Sun May 8 06:16:40 CEST 2005


Ron Adam wrote:
> I agree, re-using or extending 'for' doesn't seem like a good idea to me.

I agree that re-using a straight 'for' loop is out, due to performance and 
compatibility issues with applying finalisation semantics to all such iterative 
loops (there's a reason the PEP redraft doesn't suggest this).

However, it makes sense to me that a "for loop with finalisation" should 
actually *be* a 'for' loop - just with some extra syntax to indicate that the 
iterator is finalised at the end of the loop.

An option other than the one in my PEP draft would be to put 'del' at the end of 
the line instead of before EXPR:

   for [VAR in] EXPR [del]:
       BLOCK1
   else:
       BLOCK2

However, as you say, 'del' isn't great for the purpose, but I was trying to 
avoid introduding yet another keyword. An obvious alternative is to use 
'finally' instead:

   for [finally] [VAR in] EXPR:
       BLOCK1
   else:
       BLOCK2

It still doesn't read all that well, but at least the word more accurately 
reflects the semantics involved.

If a new keyword is used to request iterator finalisation, it should probably 
include the word 'for' since it *is* a for loop:

   foreach [VAR in] EXPR:
       BLOCK1
   else:
       BLOCK2

That is, a basic 'for' loop wouldn't finalise the iterator, but a 'foreach' loop 
would. The other difference is that the iterator in the 'foreach' loop has the 
chance to suppress exceptions other than TerminateBlock/StopIteration (by 
refusing to be finalised in response to the exception).

The last option is to leave finalisation out of the 'for' loop syntax, and 
introduce a user defined statement to handle the finalisation:

   def consuming(iterable):
       itr = iter(iterable)
       try:
           yield itr
       finally:
           itr_exit = getattr(itr, "__exit__", None)
           if itr_exit is not None:
               try:
                   itr_exit(TerminateBlock)
               except TerminateBlock:
                   pass

   stmt consuming(iterable) as itr:
       for item in itr:
           process(item)

With this approach, iterators can't swallow exceptions. This means that 
something like auto_retry() would once again have to be written as a class:

   class auto_retry(3, IOError):
       def __init__(self, times, exc=Exception):
           self.times = xrange(times-1)
           self.exc = exc
           self.succeeded = False

       def __iter__(self):
           attempt = self.attempt
           for i in self.times:
               yield attempt()
               if self.succeeded:
                   break
           else:
               yield self.last_attempt()

       def attempt(self):
           try:
               yield
               self.succeeded = True
           except self.exc:
               pass

       def last_attempt(self):
           yield


   for attempt in auto_retry(3, IOError):
        stmt attempt:
            # Do something!
            # Including break to give up early
            # Or continue to try again without raising IOError

> I wonder how much effect adding, 'for-next' and the 'StopIteration' 
> exception check as proposed in PEP340, will have on 'for''s performance.

I'm not sure what you mean here - 'for' loops already use a StopIteration raised 
by the iterator to indicate that the loop is complete. The code you posted can't 
work, since it also intercepts a StopIteration raised in the body of the loop.

> I think a completely separate looping or non-looping construct would be 
> better for the finalization issue, and maybe can work with class's with 
> __exit__ as well as generators.

The PEP redraft already proposes a non-looping version as a new statement. 
However, since generators are likely to start using the new non-looping 
statement, it's important to be able to ensure timely finalisation of normal 
iterators as well. Tim and Greg's discussion the other day convinced me of this 
- that's why the idea of using 'del' to mark a finalised loop made its way into 
the draft. It can be done using a user defined statement (as shown above), but 
it would be nicer to have something built into the 'for' loop syntax to handle it.

Cheers,
Nick.

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


More information about the Python-Dev mailing list