Re: [Python-Dev] Alternative forms [was: PEP 463: Exception-catching expressions]

2014-03-07 Thread Chris Angelico
On Sat, Mar 8, 2014 at 5:58 AM, Jim J. Jewett  wrote:
>
> (Thu Mar 6 23:26:47 CET 2014) Chris Angelico responded:
>
>> On Fri, Mar 7, 2014 at 7:29 AM, Jim J. Jewett  
>> 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

I don't see except expressions as fundamentally more associated with
if/else than with, say, an or chain, which works left to right. Aside
from both being ternary operators, of course, which isn't really much
of a justification.

>> 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.

Not sure whether the parser would be able to handle it or not, but the
human would have to if the machine can, and that's going to be a
source of confusion. I'd rather avoid it if I can. Remember,
everywhere else in Python, the word "if" is followed by something
that's interpreted as a boolean. You wouldn't expect to see this
somewhere:

if None:
do_stuff()

do_stuff() if sys else do_other_stuff()

So it'd cause a mental glitch to see some other constant and
always-true expression there:

... if ZeroDivisionError ...

You don't expect ZeroDivisionError ever to be false. Seeing it
following an if would leave you wondering if maybe it is.

IMO that syntax is abuse of the 'if' keyword, hammering it into a hole
it's not designed to fit into.

> I do think parentheses help, (but are less important when there
> is only a single "if")

Analysis of the Python standard library suggests that the single-if
situation is *by far* the most common, to the extent that it'd hardly
impact the stdlib at all to add multiple except clauses to the
proposal. Do you have a strong use-case for the more full syntax? It
tends to get long, which rather damages the readability. A number of
people have said that if the except expression goes over a line break,
it should probably be a statement instead. I'm not sure that extreme
is fully justified, but it certainly does have merit. In a codebase of
mine I wouldn't *forbid* breaking an except expression over multiple
lines, but it'd certainly be a reason to consider whether it's really
necessary or not.

> and I strongly prefer that they [the parentheses] be internal
> (which you fear looks too much like calling a function named except).
> In that case, it is:
>
> expr1 except (expr3 if expr2)

I'm still not really seeing how this is better. With the colon
version, it looks very much like dict display, only with different
brackets around it; in some fonts, that'll be very easily confused.
With the if, it looks like an incomplete expression waiting for its
else. And I'm still not enamored of constructing syntax that has the
evaluation order (a) not simple left-to-right, like most expressions
are, and (b) different from the except statement, which puts the
exception_list ahead of the suite.

> 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]

Sure. That usage makes a _lot_ of sense, I really like list
comprehensions. There's no room in them for an 'else' clause though,
so it's very strictly one-way. Makes them a tricky comparison for
this.

> 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 only confusion I have with lambda is its precedence, which trips
me up now and then:

>>> def f(x):
... return lambda y: x,y
...
>>> f(5)
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 2, in f
NameError: name 'y' is not defined

That's not returning a function that returns a tuple. It's returning a
tuple of a function and the global y, and the function will ignore its
arg.

> 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.

Not sure that really helps. This isn't going to be as tight as a

Re: [Python-Dev] Alternative forms [was: PEP 463: Exception-catching expressions]

2014-03-07 Thread Jim J. Jewett




(Thu Mar 6 23:26:47 CET 2014) Chris Angelico responded:

> On Fri, Mar 7, 2014 at 7:29 AM, Jim J. Jewett  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

Re: [Python-Dev] Alternative forms [was: PEP 463: Exception-catching expressions]

2014-03-06 Thread Chris Angelico
On Fri, Mar 7, 2014 at 7:29 AM, Jim J. Jewett  wrote:
>
> The PEP currently says:
>
>> Alternative Proposals
>> =
>
>> Discussion on python-ideas brought up the following syntax suggestions::
>
>>value = expr except default if Exception [as e]
>
> This one was rejected because of the out-of-order evaluation.
>
> Note, however, that the (farthest left) expr is always evaluated first;
> the only out-of-order evaluation is "default if Exception".
>
> "default if Exception" is precisely the same evaluation order
> (clause after the "if" skips ahead of the clause before the "if")
> as in the existing if-expression, and the existing if-filters in
> comprehensions.

Yes, but that's still out of order.

> The same justifications for that order violation generally apply here too.
> You can argue that they weren't sufficient justification in the first
> place, but that is water under the bridge; *re*-using out-of-order-"if"
> shouldn't add any additional costs.
>
> [Err... unless people take the "if" too literally, and treat the
> Exception clause as a boolean value, instead of as an argument to the
> "except" keyword.]

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. (Style guides should, of course,
decry this as unreadable, but both the machine parser and any humans
reading the code have to assume style guides mightn't be followed.)

> 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 of "if", both in its statement form and as part of "true if
expr else false", is that it is followed by something that's evaluated
as boolean; and all exception types and tuples will be true in that
context.

> 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. "Ham, if it's not
Friday; otherwise fish" is closer, or inverting that: "Fish on
Fridays, but normally ham". English is pretty flexible with how you
lay things out :)

>>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

>>value = expr except Exception [as e] continue with default
>
> This one works for me, but only because I read "continue with" as a
> compound keyword.  I assume the parser would too.  :D  But I do
> recognize that it is a poor choice for those who see the space as a
> more solid barrier.

That depends on all parsers (computer and human) being okay with the
two-keyword unit. Would have to poll human parsers to see how they
feel about it.

>>value = expr except(Exception) default # Catches only the named type(s)
>
> This looks too much like the pre-"as" way of capturing an exception.

Not sure what the connection is, but I don't like the style anyway:
putting an expression immediately after a close parenthesis seems odd
(I can't think of anything else in Python that has that).

>>value = default if expr raise Exception
>
> (Without new keyword "raises",) I would have to work really hard not to
> read that as:
>
> __temp = default
> if expr:
> raise Exception
> value = __temp

Yeah; on the flip side, "raises" doesn't add a huge amount of clarity,
and it's creating a new keyword that's not identical, but so all-but -
oh Saphir, is it not quite too "all-but"?

>>value = expr or else default if Exception
>
> To me, this just seems like a wordier and more awkward version of
>
> expr except (default if Exception [as e])
>
> including the implicit parentheses around "default if Exception".

And mainly, it abuses three keywords that can all already exist in an
expression, and doesn't use either "try" or "except". Suppose you saw
that, and wanted to kno

[Python-Dev] Alternative forms [was: PEP 463: Exception-catching expressions]

2014-03-06 Thread Jim J. Jewett



The PEP currently says:

> Alternative Proposals
> =

> Discussion on python-ideas brought up the following syntax suggestions::

>value = expr except default if Exception [as e]

This one was rejected because of the out-of-order evaluation.

Note, however, that the (farthest left) expr is always evaluated first;
the only out-of-order evaluation is "default if Exception".

"default if Exception" is precisely the same evaluation order
(clause after the "if" skips ahead of the clause before the "if")
as in the existing if-expression, and the existing if-filters in
comprehensions.

The same justifications for that order violation generally apply here too.
You can argue that they weren't sufficient justification in the first
place, but that is water under the bridge; *re*-using out-of-order-"if"
shouldn't add any additional costs.

[Err... unless people take the "if" too literally, and treat the
Exception clause as a boolean value, instead of as an argument to the
"except" keyword.]

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.  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.

(Admittedly, the word "then" *can* be elided (and represented by a slight
pause), and python programmers are already used to seeing it represented
only by ":\n")


I also give a fair amount of weight to the fact that this form starts to
look awkward at pretty much the same time the logic gets too complicated
for an expression -- that should discourage abuse.

[The analogies to if-expressions and if-filters and to spoken English,
along with discouragement for abuse, make this my preferred form.]



...
>value = expr except (Exception [as e]: default)

(and the similar but unmentioned)

value = expr except (Exception [as e] -> default)

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 do understand your concern that the parentheses make "except (...)"
look too much like a function call -- but I'm not sure that is all bad,
let alone as bad as looking like a suite introduction.

Both ":" and "->" are defined for signatures; the signature meaning
of ":" is tolerable, and the signature meaning of "->" is good.



...
>value = expr except Exception [as e] continue with default

This one works for me, but only because I read "continue with" as a
compound keyword.  I assume the parser would too.  :D  But I do
recognize that it is a poor choice for those who see the space as a
more solid barrier.


...
>value = expr except(Exception) default # Catches only the named type(s)

This looks too much like the pre-"as" way of capturing an exception.



>value = default if expr raise Exception

(Without new keyword "raises",) I would have to work really hard not to
read that as:

__temp = default
if expr:
raise Exception
value = __temp



>value = expr or else default if Exception

To me, this just seems like a wordier and more awkward version of

expr except (default if Exception [as e])

including the implicit parentheses around "default if Exception".


>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.


value = expr except Exception [as e] pass default

I would assume that this skipped the statement, like an if-filter
in a comprehension.



> All forms involving the 'as' capturing clause have been deferred from
> this proposal in the interests of simplicity, but are preserved in the
> table above as an accurate record of suggestions.

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.



> 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

which strongly resembles the suite-starter ":", but has very little
in common with the mapping ":" or the signature ":".

value = (expr except Exception) -> default

which looks like an annotation, rather than a part of the value-determination.



-jJ

--

If there are still threading problems with my replies, please
email