Comparison with False - something I don't understand

Carl Banks pavlovevidence at gmail.com
Mon Dec 6 17:42:15 EST 2010


On Dec 6, 12:58 pm, m... at distorted.org.uk (Mark Wooding) wrote:
> Paul Rubin <no.em... at nospam.invalid> writes:
> > You know, I've heard the story from language designers several times
> > over, that they tried putting resumable exceptions into their languages
> > and it turned out to be a big mess, so they went to termination
> > exceptions that fixed the issue.
>
> That seems very surprising to me.
>
> > Are there any languages out there with resumable exceptions?
>
> Common Lisp and Smalltalk spring to mind.  It's fairly straightforward
> to write one in Scheme.  (Actually, implementing the Common Lisp one in
> terms of fluids, closures and blocks isn't especially difficult.)
>
> > Escaping to a debugger doesn't really count as that.
>
> Indeed not.
>
> > I guess one way to do it would be call a coroutine to handle the
> > exception, and either continue or unwind after the continue returns,
> > but doing it in a single-threaded system just seems full of hazards.
>
> It seems pretty straightforward to me.  Handlers are simply closures;
> the registered handlers are part of the prevailing dynamic context.
> When an exception occurs, you invoke the handlers, most-recently
> registered first.  A handler that returns normally can be thought of as
> `declining' to handle the exception; a handler that explicitly transfers
> control elsewhere can be thought of as having handled it.
>
> To make this work, all you need is:
>
>   * a fluid list (i.e., one which is part of the dynamic context) of
>     handlers, which you can build in pure Python if you try hard enough
>     (see below);
>
>   * closures to represent handlers, which Python has already, and;
>
>   * a nonlocal transfer mechanism, and a mechanism like try ... finally
>     to allow functions to clean up if they're unwound.
>
> We can actually come up with a nonlocal transfer if we try, by abusing
> exceptions.
>
> [The code in this article is lightly tested, but probably contains
> stupid bugs.  Be careful.]
>
>         class block (object):
>           """
>           Context manager for escapable blocks.
>
>           Write
>
>                 with block() as escape:
>                   ...
>
>           Invoking the `escape' function causes the context body to exit
>           immediately.  Invoking the `escape' function outside of the
>           block's dynamic context raises a ValueError.
>           """
>           def __init__(me):
>             me._tag = None
>           def _escape(me, value = None):
>             if me._tag is None:
>               raise ValueError, 'defunct block'
>             me.result = value
>             raise me._tag
>           def __enter__(me, value = None):
>             if me._tag:
>               raise ValueError, 'block already active'
>             me._tag = type('block tag', (BaseException,), {})
>             me.result = value
>             return me._escape
>           def __exit__(me, ty, val, tb):
>             tag, me._tag = me._tag, None
>             return ty is tag
>
> This is somewhat brittle, since some intervening context might capture
> the custom exception we're using, but I don't think we can do
> significantly better.
>
> Implementing fluids badly is easy.  Effectively what we'd do to bind a
> fluid dynamically is
>
>         try:
>           old, fluid = fluid, new
>           ...
>         finally:
>           fluid = old
>
> but this is visible in other threads.  The following will do the job in
> a multithreaded environment.
>
>         import threading as T
>         import weakref as W
>
>         class FluidBinding (object):
>           """Context object for fluid bindings."""
>           def __init__(me, fluid, value):
>             me.fluid = fluid
>             me.value = value
>           def __enter__(me):
>             me.fluid._bind(me.value)
>           def __exit__(me, ty, val, tb):
>             me.fluid._unbind()
>
>         class Fluid (object):
>           """
>           Represents a fluid variable, i.e., one whose binding respects
>           the dynamic context rather than the lexical context.
>
>           Read and write the Fluid through the `value' property.
>
>           The global value is shared by all threads.  To dynamically
>           bind the fluid, use the context manager `binding':
>
>                   with myfluid.binding(NEWVALUE):
>                     ...
>
>           The binding is visible in functions called MAP within the
>           context body, but not in other threads.
>           """
>
>           _TLS = T.local()
>           _UNBOUND = ['fluid unbound']
>           _OMIT = ['fluid omitted']
>
>           def __init__(me, value = _UNBOUND):
>             """
>             Iinitialze a fluid, optionally setting the global value.
>             """
>             me._value = value
>
>           @property
>           def value(me):
>             """
>             Return the current value of the fluid.
>
>             Raises AttributeError if the fluid is currently unbound.
>             """
>             try:
>               value, _ = me._TLS.map[me]
>             except (AttributeError, KeyError):
>               value = me._value
>             if value == me._UNBOUND:
>               raise AttributeError, 'unbound fluid'
>             return value
>           @value.setter
>           def value(me, value):
>             try:
>               map = me._TLS.map
>               _, stack = map[me]
>               map[me] = value, stack
>             except (AttributeError, KeyError):
>               me._value = value
>           @value.deleter
>           def value(me):
>             me.value = me._UNBOUND
>
>           def binding(me, value = _OMIT, unbound = False):
>             """
>             Bind the fluid dynamically.
>
>             If UNBOUND is true then make the fluid be `unbound', i.e.,
>             not associated with a value.  Otherwise, if VALUE is unset,
>             then preserve the current value.  Otherwise, set it to
>             VALUE.
>
>             The fluid can be modified and deleted.  This will not affect
>             the value outside of the dynamic extent of the context
>             (e.g., in other threads, or when the context is unwound).
>             """
>             if unbound:
>               value = me._UNBOUND
>             elif value == me._OMIT:
>               value = me.value
>             return _FluidBinding(me, value)
>
>           def _bind(me, value):
>             try:
>               map = me._TLS.map
>             except AttributeError:
>               me._TLS.map = map = W.WeakKeyDictionary()
>             try:
>               old, stack = map[me]
>               stack.append(old)
>               map[me] = value, stack
>             except KeyError:
>               map[me] = value, []
>
>           def _unbind(me):
>             map = me._TLS.map
>             _, stack = map[me]
>             if stack:
>               map[me] = stack.pop(), stack
>             else:
>               del map[me]
>
> Now we can say
>
>         with fluid.binding(new):
>           ...
>
> and all is well.
>
> So, how do we piece all of this together to make a resumable exception
> system?
>
> We're going to need to keep a list of handlers.  We're going to be
> adding and removing stuff a lot; and we want to make use of the fluid
> mechanism we've already built, which will restore old values
> automatically when we leave a dynamic context.  So maintaining a linked
> list seems like a good idea.  The nodes in the list will look somewhat
> like this.
>
>         class Link (object):
>           def __init__(me, item, next):
>             me.item = item
>             me.next = next
>
> Our handlers are going to be simple functions which take exception
> objects as arguments.  A more advanced handler might filter exceptions
> based on their classes.  That's not especially difficult to do badly,
> but it's fiddly to do well and it doesn't shed much light on the overall
> mechanism, so I'll omit that complication.
>
> We'll want a fluid for the handler list.
>
>         HANDLERS = Fluid(None)
>
> Now we want to run a chunk of code with a handler attached.  This seems
> like another good use for a context manager.
>
>         class handler (object):
>           def __init__(me, func):
>             me._func = func
>           def __enter__(me):
>             me._bind = FluidBinding(HANDLERS,
>                                     Link(me.func, HANDLERS.value)
>             me._bind.__enter__()
>           def __exit__(me, ty, val, tb):
>             return me._bind.__exit__(ty, val, tb)
>
> (Context managers don't compose very nicely.  It'd be prettier with the
> contextmanager decorator.)
>
> Let's say that we `signal' resumable exceptions rather than `raising'
> them.  How do we do that?
>
>         def signal(exc):
>           with HANDLERS.binding():
>             while HANDLERS.value:
>               h = HANDLERS.value
>               HANDLERS.value = h.next
>               h.item(exc)
>
> Yes, if all of the handlers decline, we just return.  This is Bad for
> errors, but good for other kinds of situations, so `signal' is a
> convenient substrate to build on.
>
>         def error(exc):
>           signal(exc)
>           raise RuntimeError, 'unhandled resumable exception'
>
>         def warning(exc):
>           signal(exc)
>           ## Crank up python's usual warning stuff
>
> Note also that handlers are invoked in a dynamic environment which
> doesn't include them or any handlers added since.  Obviously they can
> install their own handlers just fine.
>
> Cool.  Now how about recovery?  This is where nonlocal transfer comes
> in.  If a handler wants to take responsibility for the exception, it has
> to make a nonlocal transfer.  Where should it go?  Let's maintain a
> table of restart points.  Again, it'll be a linked list.
>
>         RESTARTS = Fluid(None)
>
>         class restart (block):
>           def __init__(me, name):
>             me.name = name
>             super(restart, me).__init__(me)
>           def invoke(me, value = None):
>             me._escape(value)
>           def __enter__(me):
>             me._bind = FluidBinding(RESTARTS, Link(me, RESTARTS.value))
>             me._bind.__enter__()
>             return super(restart, me).__enter__()
>           def __exit__(me, ty, val, tb):
>             ## Poor man's PROG1.
>             try:
>               return super(restart, me).__exit__(ty, val, tb)
>             finally:
>               me._bind.__exit__(ty, val, tb)
>
>         def find_restart(name):
>           r = RESTARTS.value
>           while r:
>             if r.item.name == name:
>               return r.item
>             r = r.next
>           return None
>
> Using all of this is rather cumbersome, and Python doesn't allow
> syntactic abstraction so there isn't really much we can do to sweeten
> the pill.  But I ought to provide an example of this machinery in
> action.
>
>         def toy(x, y):
>           r = restart('use-value')
>           with r:
>             if y == 0:
>               error(ZeroDivisionError())
>             r.result = x/y
>           return r.result
>
>         def example():
>           def zd(exc):
>             if not isinstance(exc, ZeroDivisionError):
>               return
>             r = find_restart('use-value')
>             if not r: return
>             r.invoke(42)
>           print toy(5, 2)
>           with handler(zd):
>             print toy(1, 0)
>
> Does any of that help?

You could do that.

Or, you could just put your try...finally inside a loop.


Carl Banks



More information about the Python-list mailing list