[Python-ideas] Retrying EAFP without DRY

Chris Rebert pyideas at rebertia.com
Sat Jan 21 05:20:51 CET 2012

On Fri, Jan 20, 2012 at 4:36 PM, Mike Meyer <mwm at mired.org> wrote:
> Just running another half-baked "easy" idea up to see what others
> think of it.
> One of the cases where EAFP is clumsy is if the "forgiveness" portion
> is actually retrying what failed after fixing things. I just ran into
> this in real code, and the resulting discussion suggested this change.

I would be very interested if you give at least the general gist of
the part of your code in question. Real-world use cases are always
useful data.

> Eiffel's exception-handling mechanism is built assuming that's the
> *standard* way to do things. Seems to work ok, but if I had to choose
> one of the two, I'll take try/except.
> So here's the proposal:
> A single new keyword/clause, "retry". It has the same syntax as an
> "except" clause, can be used anywhere "except" can be used, and can be
> intermingled with them in the same try statement. There's probably a
> better syntax, but this is easy to describe.
> The behavior change from except is that instead of exiting the "try"
> statement when the "retry" clause ends, it restarts the try
> clause. In python, this code:
>    try:
>        # try block
>    except:
>        # except block
>    retry:
>        # retry block
>    else:
>        # else block
>    finally:
>        # finally block
> The use case, as mentioned, is avoiding doing EAFP without repeating
> the code in the exception handler. I.e., the choices without retry
> are things like (LBYL):
> or EAFP:
>    try:
>        mangle(x)
>    except:
>        x = fixup(x)
>        mangle(x)
>    other_stuff(x)

Right, that has evil code duplication.

> or:
>    while True:
>        try:
>            mangle(x)
>            break

Personally, I would put the `break` in a separate `else` clause.

>        except:
>            x = fixup(x)
>    other_stuff(x)

Your mention duplication as a gripe, yet there's no duplication in
this version. I take then it that you just dislike this idiom?

> with:
>    try:
>        mangle(x)
>    retry:
>        e = fixup(x)
>    other_stuff(x)

Do I misunderstand your proposal, or are you missing an "except:
retry" here? If you aren't, then how is infinite-retrying avoided?

I would think that in many (most?) cases, we'd like to cap the number
of reattempts.
Currently, this looks like:

for _ in range(5):
        buy_off_ebay(item, price)
    except OutbidError:
        price = highest_bid_for(item) * 1.05
        # continue
# Some fiddling can be had over where to put the `break`
# and whether to include an explicit `continue`.

With what I understand `retry` would be:

# Not even bothering with the capping
    buy_off_ebay(item, price)
except OutbidError:
    price = highest_bid_for(item) * 1.05
# I'd have to maintain the count myself?!
# That's easily going to squander the 1 line this saved.

I grant you that this has saved 1 level of indentation, whatever
that's worth (personally, I don't put enough premium on it in this
case to justify new syntax). It's also slightly more explicit (once
you know what `retry` does, of course). And, in more general cases, a
very few lines can be saved.

But when one gets to the more general, complicated cases, it seems to
me that you're better off just writing and making use of your own
try_N_times(make_bid, N) [or whatever your "keep retrying" heuristic
is] function, which argues against adding new syntax for this.

> My gut reaction is cool, but not clear it's worth a new keyword. So
> maybe there's a tweak to the except clause that would have the same
> effect, but I don't see anything obvious.

My view on Python's choice of control structures is that it provides a
near-minimal, simple (so long as you overlook the obscure and
seldom-used `else` clause of `for` & `while`) set of them that you can
then combine to achieve fancier behavior (e.g. while + try + break =
retry). For example, there's no do-while (instead: while True +
if-break); there's no unless/until (instead: while/if + not); there's
no C-style `for` (instead: for+range(), or a custom while); there's no
`redo` (instead: nested while True + if-break/continue); and thus, no
`retry` (instead: nested try within a loop).

This keeps the core language small, aesthetically clean, uniform, and
easy-to-learn. If one understands `if`, `while`, `for`, `try`,
`break`, and `continue` (and `not`, but that's merely a boolean
operator, as opposed to a control structure), then one can easily work
out from scratch what the idioms do, and one learns to recognize them
with just a bit of practice/experience. Also, it's easy to tweak the
idioms when your control flow changes or becomes more complicated,
thus allowing for flexibility.

On the other hand, some clarity/explicitness is definitely lost.
There's a design trade-off here; I don't think Python has tended
towards the side that would add `retry`.


More information about the Python-ideas mailing list