[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