On 26/10/2021 02:56, Steven D'Aprano wrote:
On Tue, Oct 26, 2021 at 01:32:58AM +0100, Rob Cliffe via Python-ideas wrote:

Syntax bikeshedding: I still favour
     var := expr
That clashes with the walrus operator. Remember that the walrus operator
can appear inside the expression:

     var:=spam+eggs:=(something+other) or eggs
That is a SyntaxError.  I'm not sure what you mean, my best effort to make it legal is
        (var:=spam+(eggs:=(something+other) or eggs))
And I don't understand what point you're making here.  Yes, the walrus operator can appear in various places, how is that relevant? You could write
    def f(a := (b := c)):
which might be a tad confusing but would be unambiguous and legal, just as
    def f(a = (b := c)):
is currently legal (I tested it).  I don't see a clash.

Modifying the assignment symbol is wrong. This is not a new kind of
assignment, it should use the same `=` regular assignment. We are
tagging the parameter to use late-binding, not using a different sort of
assignment. The tag should be on the parameter name, not the assignment.
With respect, it IS a new kind of assignment.  One which happens at a different time (and whose value may vary in multiple calls of the function).  The value (however calculated) is assigned to the parameter.  Once assigned, the parameter and its value are indistinguishable from ones that used early binding, or indeed had a value passed by the caller.  It is not a new kind of parameter (in that sense).


IMO the similarity to early binding syntax is a good thing (or at least
not a bad thing).
Right, because binding is binding, and we should use the same `=`.
See above.


  Just as the walrus operator is similar to `=` - after
all they are both a form of assignment.
But the walrus is a different form of assignment, it is an expression,
not a statement.
True of course.  But it is also an assignment, in that the value on the RHS of the walrus is assigned to the variable on the LHS, just as with a regular assignment.
  Function parameter defaults are not literally
statements, they are declarations, which are a kind of statement.


I don't think argument defaults should be allowed to refer to later
arguments (or of course the current argument).  That's making the
interpreter's task too complicated, not to mention (surely?)
inefficient.  And it's confusing.
Worst case, the interpreter has to do two passes over the parameters
instead of one. The inefficiency is negligible.
Perhaps I wasn't clear.  When I said 'inefficiency', I meant to refer to cases like
    def f(a := b+1, b := e+1, c := a+1, d := 42, e := d+1)
where late-binding defaults are allowed to refer to subsequent arguments.  Here Python has to work out to assign first to d, then e, then b, then a, and finally c, which AFAICS requires multiple passes.  But if it can all be worked out at compile time, it's not a runtime efficiency problem, though to my mind really obfuscated (why not write the arguments in the order they are intended to be calculated?).
But to play Devil's Advocate for a moment, here is a possible use case:
    def DrawCircle(centre, radius := circumference / TWO_PI, circumference := radius * TWO_PI):         # Either radius or circumference can be passed, whichever is more convenient
As for confusing, I think you are conflating "it's new" for "it is
confusing". You aren't confused. I doubt that anyone capable of writing
a Python function would be confused by the concept:

     def func(a=1, @b=c+1, c=2):

is no more confusing than the status quo:

     def func(a=1, b=None, c=2):
         if b is None:
             b = c + 1
Confusing is perhaps the wrong word.  I think the first example IS harder to read.  When you read the first, you have to read a, then b, then 'oh what is the default value of b, I'll look at c', then skip back to b to see what the intention is, then forward again to c because you're interested in that too.  It would be better written as
        def func(a=1, c=2, @b=c+1):
There is some to-and-froing in the second example too, but the function header has fewer symbols and is easier to take in.  The information is presented in more, smaller chunks (3 lines instead of 1). (Of course, this kind of argument could be used against all argument defaults (including early-bound ones), and a lot of other convenient language features as well.  We have to use our common sense/intuition/judgement to decide when conciseness outweighs explicitness.)

If you can understand the second, you can understand the first. All you
have to remember is that:

1. positional arguments are bound to parameters first, left to right;

2. keyword arguments are bound to parameters next;

3. regular (early bound) defaults are bound next;

4. and lastly, late-bound defaults are bound.

Easey-peasey.

I really wish people would stop assuming that fellow Python coders are
knuckle-dragging troglodytes incapable of learning behaviour equivalent
to behaviour they have already learned:
I don't.  But equally I don't want to make their lives harder than they need be.

If it turns out that binding all early-bound defaults before binding all late-bound defaults is the best solution (and there is support for this position), fine.  It makes some cases legal and working that wouldn't be otherwise.  My initial reaction (preferring strict L-to-R evaluation of all defaults) was very likely wrong.

The status quo:

1. positional arguments are bound to parameters first, left to right;

2. keyword arguments are bound to parameters next;

3. regular (early bound) defaults are bound last.

All we're doing is adding one more step. If that is confusing to people,
wait until you discover classes and operator precedence!

     x = 2*3**4 - 1
FWIW I have no trouble parsing that (although I came up with an incorrect answer of 80 because I forgot to actually do the `2*` 🙁).

[snip]

(Maybe even as late as *on need* rather than before the body of the
function is entered. That would be really nice, but maybe too hard to
implement.)
Never mind hard-to-implement, it would be REALLY confusing.  When the default value is calculated up front, you know what it is (so to speak).  If it could be calculated deep inside the function, perhaps in multiple places giving different answers, perhaps used repeatedly in a loop (but only calculated the first time), perhaps not calculated at all in some code paths, that's obfuscation and abuse. Better to use a sentinel value.  Or something.
Rob Cliffe
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/CJVTFU5Y3T4OHUEP2NJBMUFGX47AIPW2/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to