Abstracting try..finally with generators (instead of macros)

Beni Cherniavsky cben at techunix.technion.ac.il
Sun Dec 15 09:47:20 EST 2002


On 2002-12-14, Andrew Dalke wrote:

> Beni Cherniavsky:
>  > I believe I've just discovered a new cool use for generators, one that
>  > people thought they need macros for [1].
>
> Cool thought.Though you could also have done
>
> import sys
> class withmyout_safe3:
>  def __init__(self, handle):
>    self.flg = 0
>    self.saved = sys.stdout
>    sys.stdout = handle
>  def __getitem__(self, n):
>    if self.flg:
>      raise IndexError
>    self.flg = 1
>  def __del__(self, sys = sys):
>    # Need a reference to 'sys' (I think) in case this is a variable
>    # in module scope and the sys module gets cleared before the module
>    # containing the variable.  OTOH, I think sys is one of the ones
>    # which may be guaranteed to clean up last?
>    sys.stdout = self.saved
>
True, I forgot about for's fallback to `__getitem__`.  However the code
doesn't seem to be simpler (maybe even worse) and IIRC, for is creating a
lightweight wrapper iterator in such cases, so the performance will be
worse too.  Anyway writing a class is much less readable than a generator,
for which I would go, disregarding the performance of the safety
wrappers...  A try..finally around yield is still needed.  Anyone with an
idea of how complex it would be to implement?

About the `sys` reference: I think that the class, being defined in the
module (and referncing global variables) retains the module -- which in
turn retains sys by having it imported -- so there shouldn't be a problem.
Do I miss anything?

----

Other issues: Paul Foley noted that the dynamic binding of `sys.stdout`
works only without threads.  True.  I would also add that one can't use
``for dummy in withmyout_safe():`` in generators around yields, or
anywhere if some generators.  My code was just and example of any
side-effects that one would want to wrap before/after pieces of code.
Obviously you need to think first if these side effects are what you
really want...  I have some ideas for per-thread/generator dynamic binding
which I'll try to post later; the interested can google for ``fluid-let
thread`` for insight...

He also mentioned that the dependence on refcounting is problematic.
OOPS...  I was under the impression that refcounting is considered
reliable in python, i.e. objects are promised to be deleted when all
references are gone.  I've just checked Jython and it doesn't promise it.
:-( indeed.  So I currently don't see a way that my idiom can work
reliably in Jython.  This renders my idea from a solution to hack that
works in current CPython but isn't acceptable as an idiom to recommend to
people.  That basically leaves the language again in want of a way to
abstract try..finally patterns.  :-{.  Anybody sees a way to fix it?
Can yield in try..finally be implemented reliably in Jython?  This would
probably require the for to explicitly throw an exception into the
iterator (compare PEP 288).

Observation: the ``for`` statement is very special for allowing my idea.
I tried to invent new control generator idioms, for example to solve the
do-while popular request.  For a moment I thought of:

def true_once():
    yield 1
    while 1:
        yield 0

while true_once() or <condition>:
    <body>

Obviously, this can't work so simply because I need to separate calling
the generator once from calling `.next()` every iteration (i.e. I need an
extra line before the while -- there goes the saving).  Only ``for`` has a
separate "setup" stage that allows such tricks:

def do_while(cond):
    yield None
    while cond():
        yield None

for dummy in do_while(lambda: <condition>):
    <body>

Not that it's that beautiful, I'll carry on with ``while 1`` and
``if <cond>: break``...

This is a special point about ``for`` also because the iterator is stored
unnamed.  I think that if it would have a name in the locals, the iterator
would be retained in a backtrace and even the above hack would fail...

Last, I forgot to ask about ``yield`` to be a shortcut for ``yield None``,
like with ``return``...

-- 
Beni Cherniavsky <cben at tx.technion.ac.il>












More information about the Python-list mailing list