[Python-Dev] Alternative forms [was: PEP 463: Exception-catching expressions]
Jim J. Jewett
jimjjewett at gmail.com
Fri Mar 7 19:58:25 CET 2014
(Thu Mar 6 23:26:47 CET 2014) Chris Angelico responded:
> On Fri, Mar 7, 2014 at 7:29 AM, Jim J. Jewett <jimjjewett at gmail.com> wrote:
>> [ note that "x if y" already occurs in multiple contexts, and
>> always evaluates y before x. ]
> Yes, but that's still out of order.
Yeah, but local consistency is more important than global guidelines. :D
>> ... *re*-using out-of-order-"if" shouldn't add any additional costs.
> The other thing to note is that it's somewhat ambiguous. Until you
> find that there isn't an else clause, it could be the equally valid
> "expr except (default if cond else other_default)", with the actual
> "if Exception" part still to come.
True -- and I'm not a parser expert. But my belief is that the
current parser allows lookahead for exactly one token, and that
the "else" would fit within that limit.
> ... humans reading the code have to assume style guides mightn't
> be followed.
True ... but I hope any non-trivial use of this (including use
with a non-trivial ternary if) will look bad enough to serve as
its own warning.
>> The advantages of this form get much stronger with [as e] or
>> multiple different except clauses, but some of them do apply
>> to even the simplest form.
> Multiple different except clauses would make for an even
> messier evaluation order:
> expr1 except expr3 if expr2 except expr5 if expr4
> If you consider the exception type to be the condition, then
> this makes sense (that is, if you read it as
> "if isinstance(thrown_exception, Exception)");
> [but the most obvious reading is boolean; as always True]
I phrased that badly. I agree that without parentheses for
good spacing, the above is at least ambiguous -- that is what
you get for stringing multiple clauses together without
internal grouping.
I do think parentheses help, (but are less important when there
is only a single "if") and I strongly prefer that they be internal
(which you fear looks too much like calling a function named except).
In that case, it is:
expr1 except (expr3 if expr2)
and the extension to multiple except clauses would be:
expr1 except (expr3 if expr2,
expr5 if expr4)
though as I discuss later, placing parentheses there also makes a
colon or arrow more tolerable. It does this because the nearby
parens make it look more like the existing (non-lambda) uses of
inline-colon to associate the two things on either side. (Without
nearby brackets, the scope of the colon or arrow is more easily
taken to be the whole line.)
expr1 except (expr2: expr3,
expr4: expr5)
expr1 except (expr2 -> expr3,
expr4 -> expr5)
>> Notably, the "say it like you would in English" that convinced
>> Perl still applies: "if" *without* a "then" is normally an extra
>> condition added after the main point:
>> Normally ham, but fish if it's a Friday.
> That's not how Python words ternary if, though.
Agreed ... the "say it like you would in English" applies only
to the "expr if expr" form (proposed here and) used by comprehensions:
[1/x for x in data
if x]
>> value = expr except (Exception [as e]: default)
>
> (and the similar but unmentioned)
>
> value = expr except (Exception [as e] -> default)
The parenthesizing question and the choice of tokens are considered
independent, so not all the cross-multiplications are listed.
>> The mapping analogy for ":" is good -- and is the reason to place
>> parentheses there, as opposed to around the whole expression. Your
>> preferred form -- without the internal parentheses -- looks very
>> much like a suite-introduction, and not at all like the uses
>> where an inline colon is acceptable.
> I have some notes on that down the bottom:
> http://www.python.org/dev/peps/pep-0463/#colons-always-introduce-suites
I know that they don't always introduce suites.
I can't speak to the lambda precedent, but I do know that I personally
often stumble when trying to parse it, so I don't consider it a good model.
The other three inline uses (dict display, slide notation, and
function parameter annotation) are effectively conjunction operators,
saying that expr1 and expr2 are bound more tightly than you would
assume if they were separated by commas. They only occur inside
a fairly close bracket (of some sort), and if the bracket isn't
*very* close, then there are usually multiple associates-colons
inside the same bracket.
data[3:5]
data[-1:-3:-1]
def myfunc(a:int=5,
b:str="Jim",
c:float=3.14)
{'b': 2, 'c': 3, 'a': 1}
With parentheses after the except, the except expression will match
this pattern too -- particularly if there are multiple types of
exception treated differently.
expr1 except (expr2: expr3)
Without (preferably internal) parentheses, it will instead look like
a long line with a colon near the end, and a short continuation suite
that got moved up a line because it was only one statement long.
def nullfunc(self, a): pass
expr1 except expr3: expr2
>>> value = expr except Exception [as e] -> default
>>
>> Without parens to group Exception and default, this looks too much like
>> an annotation describing what the expr should return.
> Can expressions have annotations?
Not yet. I have seen proposals. Other syntax (notably, Decorations)
have expanded their domain. I would be mildly surprised if there isn't
already a 3rd party template system using -> in places where it isn't
currently defined.
>>> All forms involving the 'as' capturing clause have been deferred ...
>> Nick is right that you should specify whether it is deferred or
>> rejected, because the simplest implementation may lock you into
>> too broad a scope if it is added later.
> Put it this way: It's deferred until there's a non-closure means of
> creating a sub-scope.
The problem is that once it is deployed as leaking into the parent
scope, backwards compatibility may force it to always leak into
the parent scope. (You could document the leakage as a bug or as
implementation-defined, but ... those choices are also sub-optimal.)
>>> The four forms most supported by this proposal are, in order::
>>> value = (expr except Exception: default)
>>> value = (expr except Exception -> default)
>> If there are not parentheses after "except", it will be very tempting
>> (and arguably correct) to (at least mentally) insert them around the
>> first two clauses -- which are evaluated first. But that leaks into
>> value = (expr except Exception): default
...
> You can put parens around true_expr or cond, no problem, but you
> can't put them around both:
> >>> value = (true_expr if cond) else false_expr
> SyntaxError: invalid syntax
The fact that short-circuiting will prevent false_expr from even
being evaluated means that "(true_expr if cond)" is a useful
mental approximation, even though "true_expr if cond" is already
to the left.
Commutivity and Associativity are pretty strong biases; testing
order of execution for apparently parallel expressions (such as
different arguments to the same function in the same call) is a
source of trick questions.
When associativity is violated, computation normally proceeds
from the left, and any time you need to jump ahead to simplify
something that isn't left-most, that special case needs to be
signalled. For short enough jumps, order of operations can
be sufficient (e=mc^2), but you'll still see plenty of programmers
inserting redundant parentheses just to make things clear (or
maybe just to avoid having to look up the order of operations).
The replacement value and the exception that triggers it are
clearly associated more closely with each other than either is
to the primary expression. Therefore, they *should* be grouped
more tightly.
This is a proposal with no perfect syntax, but I don't really
see any upside to *not* grouping the exception with its
exceptional result. At the moment, I don't know how to spell
that closer grouping without at least one of:
Parentheses or brackets of some sort
A line break/new suite:
- kills the proposal
An inline associates-colon:
- needs brackets anyhow, or it will be mistaken for
a suite-introduction-colon.
A new grouping operator, such as "->"
- risk that it will be misinterpreted
Relying on convention and taste:
(main_expr except exc_expr fallback_expr)
-jJ
--
If there are still threading problems with my replies, please
email me with details, so that I can try to resolve them. -jJ
More information about the Python-Dev
mailing list