On 08/08/2020 02:16, Steven D'Aprano wrote:
On Fri, Aug 07, 2020 at 06:40:33PM +0100, Rob Cliffe via Python-ideas wrote:

On 07/08/2020 17:16, Serhiy Storchaka wrote:

      
The main problem to me with the exception catching expression is that
you need to add the exception name and several keywords, and it makes
real-world examples too long, so you will need to split the expression
on several lines, and add extra parenthesis. And in this case there is
no much difference between expression

    x = (
        <long expression>
	except LongExceptionName:
        <other long expression>
    )

and statement

    try:
        x = <long expression>
    except LongExceptionName:
        x = <other long expression>

(actually the statement may be shorter and better aligned).
Serhiy is correct that some examples will be long and cumbersome, but 
many other expressions are long and cumbersome, and we leave it up to 
the writer of the code to decide whether to extend a single expression 
over multiple lines or split it into multiple statements using temporary 
variables.

In Serhiy's example above, it is up to the coder and their aesthetic 
sense to decide whether to use the expression form or the statement 
form, just as today they can decide between:


    if <very long condition>:
        x = <long expression>
    else:
        x = <another long expression>


and


    x = (<long expression>
         if <very long condition>
         else <another long expression>
         )


Neither is necessarily better or worse than the other, it will depend on 
the surrounding code, whether the value is being bound to a variable or 
being immediately used in another expression:

    function(arg,
             another_arg,
             spam=long_conditional_or_except_expression,
             eggs=expression
             )


Just like if statements vs ternary if expressions, the writer will get 
to choose which is better for their purposes, and whether or not to use 
temporary variables.


This is a strawman argument.  You set up a case where exception-catching 
expressions are poor (long expression, long exception name) and then 
knock it down.
Please don't misuse "strawman argument" in this way. Serhiy is making a 
legitimate criticism of the feature, and explicitly labelling it as a a 
reason he personally does not like the feature.

It is not a strawman to point out your personal reasons for disliking a 
feature. This is a legitimate issue with the suggested syntax: it is not 
especially terse, and if the expressions and exceptions are long, as 
they sometimes will be, the whole thing will be little better, or not 
better at all, than using a try...except statement.
Yes but Serhiy implied that the whole thing will *always* be too long, which won't be true.  He said, without qualification, "it makes real-world examples too long".

You don't have to agree with Serhiy's preference to recognise that there 
are cases where this proposal will save no lines of code. And probably 
not rare cases -- I expect that they will be much more common than the 
short examples you quote:

If you read the PEP you will find plenty of short examples:

	process(dic[key] except KeyError: None)
	value = (lst[2] except IndexError: "No value")
	cond = (args[1] except IndexError: None)
	pwd = (os.getcwd() except OSError: None)
	e.widget = (self._nametowidget(W) except KeyError: W)
	line = (readline() except StopIteration: '')
etc.
The first is a poor example because that can so easily be written as:

    process(dic.get(key))

The second and third are basically the same example.

The fourth example of os.getcwd is excellent. But the fifth example 
strikes me as exceedingly weak. You are calling a private method, which 
presumably means you wrote the method yourself. (Otherwise you have no 
business calling the private method of a class you don't control.) So 
why not just add an API for providing a default instead of raising?

And the final example is also weak. If readline is a bound method of a 
file object:

    readline = open(path, 'r').readline

then it already returns the empty string at the end of file. And if it 
isn't, then perhaps it should, rather than raising a completely 
inappropriate StopIteration exception.
I just copied the first few examples from the PEP without analysing them, so you are criticising the PEP, not me.  I hope Chris Angelico appreciates the criticism (I mean that seriously, not as a piece of sarcasm).

Other problem specific to the PEP 463 syntax is using colon in
expression. Colon is mainly used before indented block in complex
statements. It was introduced in this role purely for aesthetic reasons.
I disagree with Serhiy here: I believe that there is objective evidence 
in the form of readability studies which suggest that colons before 
indented blocks aid readability, although I have lost the reference.


Using it in other context is very limited (dict display, lambda,
annotations, what else?).

Slice notation.  As you would have discovered if you read the PEP.
No need to be so catty over a minor bit of forgetfulness, I'm sure that 
Serhiy has read the PEP, and we know that Serhiy is experienced enough 
to have seen slice notation a few times. Let's not condemn him for a 
momentary bit of forgetfulness.
You're right.

I apologise, Serhiy.


Dict display and slices are hardly of "very limited" use.  (I can't 
comment on annotations, I don't use them.)
I don't believe that Serhiy is referring to how frequently slices or 
dicts are used, but to the *very limited number of contexts* where 
Python uses colons:

- to introduce an indented block
- dict displays {key: value}
- slices obj[2:7]
- lambda arg: expression
- annotations def spam(arg:int)

So I think that there are only five places where colons are used, and 
only three of them are part of expressions. Out of the dozens of 
syntactic forms in Python, I think that's quite limited.
Surely if you are going to argue against a colon in the middle of a line on the grounds that that is unexpected/jarring/whatever, what matters is how often we see it in code, not how many categories it fits into.  I did a quick token analysis of the 3.8.3 stdlib and found that approximately 80% of colons introduced a suite, 20% didn't.  But FWIW there are AFAIK 10 keywords that introduce a suite (def, elif, else, except, finally, for, if, try, while, with), or alternatively 6 constructs that use suites (for, while, if..., try..., def, with).
Whichever way you cut it (80% to 20%, or 8 to 4, or 6 to 4), a majority of colons do introduce a suite, but not an overwhelming majority.  It is not true that "colons almost always introduce a suite" or "Using it in other contexts is very limited".

(But not as limited as I would have guessed ahead of time.)