[Python-ideas] except expression

Chris Angelico rosuav at gmail.com
Thu Feb 20 17:13:19 CET 2014


On Fri, Feb 21, 2014 at 2:24 AM, Yann Kaiser <kaiser.yann at gmail.com> wrote:
> I too am uncomfortable with re-purposing except ...: from introducing
> a block to introducing either a block or an expression. "Keyword then
> colon" is a great indicator for when you should start a new indent,
> especially with regards to learners. Lambda undermines this enough as
> it is, and dicts/slices don't have a keyword preceding them.

"Keyword then colon at the end of a line" is still a good indicator
for indenting. If it's not the end of a line, you can't start
indenting anyway. Proper layout of an expression-except that goes
across multiple lines is still open for debate, but the most common
cases will fit onto a single line anyway. The same applies to lambda
and dict expressions, the colon won't end the line.

Incidentally, quite a few of Python's control structures don't
actually have "keyword then colon" anyway:

for NAME in EXPR:
if EXPR:
while EXPR:
except EXPR:
except EXPR as NAME:

So looking for a syntax-highlighted bit followed by a colon will pick
up only a subset of cases anyway.

> I think we aren't going deep enough into the rabbit hole. Why restrain
> ourselves to trying to cram try-except-finally into an expression,
> losing the finally part, when we already have a tool right under our
> eyes, as Nick Coghlan pointed out, which leverages try-except-finally
> entirely in a concise fashion, the with statement.
>
> If context managers are amended to support a return value(maybe even
> transforming one?), a with-expression could look like:
>
>     last = L.pop() with default(None, IndexError)
>     x = expr with produce_default(expensive_op, AnException)
>     contents = f.read() with open('filename') as f
>     d = Decimal(1) / Decimal(7) with Context(prec=5)
>
> The syntax is cleaner, including when easing the original problem, and
> is capable of so much more. I seem to be lacking modesty today,
> because it seems like this is worth scrapping except-expression for
> it.

That smells like a very different proposal :)

If you want to champion that one, knock together some examples
(contrived or concrete) and start suggesting an expression-with
construct, which could then, as you say, subsume this proposal. Be
aware, though, that it's going to need some fancy magic to handle lazy
evaluation of the default. Compare these constructs:

# Basic notation, works fine
# Note: Don't assume anything about what x is.
# x could literally be any object in the system.
try:
    x = cache[key]
except LookupError:
    # This computation is expensive, might require
    # network traffic even.
    x = compute_and_cache(key)

# Currently legal but eagerly calculates default
# Also depends on cache being an actual dict
x = cache.get(key, compute_and_cache(key))

# Proposed except-expression
x = cache[key] except LookupError: compute_and_cache(key)

# with-statement
with exception_yields_default(LookupError, lambda: compute_and_cache(key)):
    x = cache[key]
    # If it fails, get the value from the context manager somehow

It's plausible, but you'd have to have some system for ensuring lazy
calculation. You'll find a vaguely effective helper-function example
in the PEP; it's a bit ugly and uses lambdas everywhere, but it would
be properly lazy.

Here's another example of where lazy evaluation is important:

try:
    opt = config[key]
except LookupError:
    opt = input("Enter "+key+": ")

opt = config[key] except LookupError: input("Enter "+key+": ")

How would you do this with an expression form of with? Make sure it
looks clean and evaluates lazily.

ChrisA


More information about the Python-ideas mailing list