[Python-ideas] except expression

Steven D'Aprano steve at pearwood.info
Mon Feb 17 05:46:02 CET 2014


On Mon, Feb 17, 2014 at 09:19:22AM +1100, Chris Angelico wrote:
> On Mon, Feb 17, 2014 at 7:39 AM, Jan Kaliszewski <zuo at chopin.edu.pl> wrote:
> > Sorry, I don't catch the point.  If I needed to use a complex
> > exception spec (a tuple-like) and/or a tuple as the "default"
> > expression -- I'd just do it:
> >
> >     some_io() except (FileNotFoundError: (1, 2, 3),
> >                       (ValueError, TypeError): 'spam')
> >
> > I see no ambiguity here.
> 
> Maybe not, but the only thing preventing that from parsing as a tuple
> containing two tuples is the colon a bit further along. That might be
> sufficient for the lexer (in the same way that, for instance, function
> arguments can contain tuples), but I suspect it may be confusing for
> humans.

It's certainly confusing for me!

I'm now strongly leaning towards following the lead of generator 
expressions, and requiring parens around an except-expression. Like gen 
expressions, you can leave the parens out if they are already there:

    it = (calc(x) for x in iterable)  # parentheses are mandatory
    result = sum(calc(x) for x in iterable)  # but this is okay

So if your except-expression stands alone, or is inside some other 
unbracketed expression, you need to bracket it:

    value = (expr except Error: default)
    mytuple = (1, 2, (expr except Error: default), 3)


But if it's already directly surrounded by parentheses, say inside a 
function call with no other arguments, there is no need to double-up:

    result = function(expr except Error: default)


I think that this will ensure that there is no visual ambiguity when you 
chain except-expressions. Even if the parser/lexer can disambiguate the 
expression, it risks being completely impenetrable to the human reader.
Take this case, where I'm labelling parts for ease of discussion:

    # What does this do?
    expr except SpamError: spam except EggsError: eggs
    #^^^^^FIRST^^^^^^^^^^^^^^^^ ^^^^^^SECOND^^^^^^^^^^

Does the SECOND except capture exceptions in the evaluation of "spam" 
only, or in the entire FIRST except? If this question doesn't make sense 
to you, then please ponder these two examples:

    ((expr except SpamError: spam) except EggsError: eggs)
    (expr except SpamError: (spam except EggsError: eggs))


Requiring parens should also avoid the subtle error of leaving out a 
comma when you have multiple except-clauses:

    expr except SpamError: spam, except EggsError: eggs
    #..........................^

is a single expression that catches two exceptions, equivalent to:

    try:
        _ = expr
    except SpamError:
        _ = spam
    except EggsError:
        _ = eggs


whereas leaving out the comma:

    expr except SpamError: spam except EggsError: eggs

is *two* except-expressions, equivalent to either this:

    try:
        try:
            _ = expr
        except SpamError:
            _ = spam
    except EggsError:
        _ = eggs


or this:

    try:
        _ = expr
    except SpamError:
        try:
            _ = spam
        except EggsError:
            _ = eggs


depending on how strongly the except binds.

The with- and without-comma versions are very subtly different, and 
consequently a potential bug magnet, from a very subtle and easy-to-make 
typo. By requiring parens, you can avoid that. The first version 
becomes:

    (expr except SpamError: spam, except EggsError: eggs)

and dropping the comma is a SyntaxError, while the second version 
becomes either:

    ((expr except SpamError: spam) except EggsError: eggs)
    (expr except SpamError: (spam except EggsError: eggs))


depending on which semantics you want.



-- 
Steven


More information about the Python-ideas mailing list