[Python-Dev] PEP 340 - possible new name for block-statement

Nick Coghlan ncoghlan at gmail.com
Fri Apr 29 17:00:44 CEST 2005


Pierre Barbier de Reuille wrote:
> One main reason is a common error could be (using the synchronised 
> iterator introduced in the PEP):
> 
> for l in synchronised(mylock):
>   do_something()
> 
> It will compile, run, never raise any error but the lock will be 
> acquired and never released !

It's better than that. With the code above, CPython is actually likely to 
release the lock when the loop exits. Change the code to the below to ensure the 
lock doesn't get released:

   sync = synchronised(mylock):
   for l in sync:
       do_something()

> Then, I think there is no use case of a generator with __error__ in the 
> for-loop as it is now. So, IMO, it is error-prone and useless to have 
> two different syntaxes for such things.

Hmm. This does make PJE's suggestion of requiring a decorator in order to flag 
generators for finalisation a little more appealing. Existing generators 
(without the flag) would not be cleaned up, preserving backwards compatibility. 
Generators with the flag would allow resource clean up.

In this case of no new statement syntax, it would probably make more sense to 
refer to iterators that get cleaned up as finalised iterators, and a builtin 
with the obvious name would be:

     def finalised(obj):
         obj.__finalise__ = True  # The all important flag!
         return obj

The syntax below would still be horrible:

     for f in opening(filename):
         for line in f:
            # process line

But such ugliness could be fixed by pushing the inner loop inside the block 
iterator:

    for line in opened(filename):
       # process line

    @finalised
    def opened(filename):
        f = open(filename)
        try:
            for line in f:
                yield line
        finally:
            f.close()

Then, in Py3K, finalisation could simply become the default for loop behaviour. 
However, the '__finalise__' flag would result in some impressive code bloat, as 
any for loop would need to expand to:

     itr = iter(EXPR1)
     if getattr(itr, "__finalise__", False):
         # Finalised semantics
         #    I'm trying to channel Guido here.
         #    This would really look like whatever the PEP 340 block statement
         #    semantics end up being
         val = arg = None
         ret = broke = False
         while True:
             try:
                 VAR1 = next(itr, arg)
             except StopIteration:
                 BLOCK2
                 break
             try:
                 val = arg = None
                 ret = False
                 BLOCK1
             except Exception, val:
                 itr.__error__(val)
             if ret:
                 try:
                     itr.__error__(StopIteration())
                 except StopIteration:
                     pass
                 return val
     else:
         # Non-finalised semantics
         arg = None
         while True:
             try:
                 VAR1 = next(itr, arg)
             except StopIteration:
                 BLOCK2
                 break
             arg = None
             BLOCK1

The major danger I see is that you could then write a generator containing a 
yield inside a try/finally, _without_ applying the finalisation decorator. 
Leading to exactly the problem described above - the lock (or whatever) is never 
cleaned up, because the generator is not flagged for finalisation. In this 
scenario, even destruction of the generator object won't help.

Cheers,
Nick.

P.S. I think PEP 340's proposed for loop semantics are currently incorrect, as 
BLOCK2 is unreachable. It should look more like the non-finalised semantics 
above (with BLOCK2 before the break in the except clause)

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


More information about the Python-Dev mailing list