This was probably mentioned at some point (apologies, can't afford to
read the entire thread), but since the issue of left-to-right vs.
early-first-then-late binding was hotly debated, I just want to point out
that left-to-right better preserves scoping intuitions:
foo = 42
def bar(@baz=foo, foo=1):
return baz, foo
With left-to-right, bar() returns (42, 1) (or whatever other value the
foo global currently has). With early-first-then-late, it returns (1,
1). And according to the current version of the PEP, it's essentially
undefined behavior, though AFAICS, the possibility that baz would take
its value from global foo isn't even considered.
As a teacher, I'd much rather teach/explain the first option, because
in most of Python, you can figure out what value a name refers to by
looking to the left and above of where it's used, and if you can't find
it in the local scope, check globals.
(One prominent place where this doesn't hold is comprehensions/genexprs,
which in practice tend to be a learning bump for beginners.)
As for errors -- if SyntaxError were used, then the example above would
raise it, wouldn't it? Because there's no way to statically determine
whether foo might also be a global, in addition to being a function
parameter. So there would be no way of having a late-bound parameter
take a default from a global, while having an early-bound parameter with
the same name as the global. I freely acknowledge you probably won't
need this most of the time, but why prohibit it outright (or why make it
into undefined behavior, without even considering the global, as the PEP
currently does), especially since it aligns with scoping intuitions as
outlined above?
Finally, as hinted by the example, I'm leaning towards the @ notation.
=> is pointing in the wrong direction, when programming languages use
some kind of arrow-like operator for assignment, it tends to point
towards the name, not the value. (I realize this is not really
assignment, but the syntactic analogy is clear.) Whereas I can easily
see myself telling students that the @ acts as a sort of barrier to
eager evaluation/assignment of the default value (as a mnemonic).
It could also be written after the =, as in baz=@foo, which reads like a
quote operator and is open to future extensions where unary @ could be
used to defer evaluation in more places (as some people seem to want --
I personally am wary of quote/eval/substitute shenanigans, such as R
has). The barrier mnemonic could remain the same. OTOH, I like the way
the prefix is more conspicuous and visually signals that the parameter
is special/different right off the bat.
The PEP doesn't have any examples of what the syntax for *assigning*
late bound arguments is, so I'm assuming there's no special syntax? As
in, bar can be called e.g. as bar(1, 2), bar(baz=1, foo=2) or bar(foo=1,
baz=2)? That makes sense, but I think it's a small argument in favor of
a prefix notation, so that = is kept the same across definitions and
calls. Somehow, I would expect my students to be more likely to
erroneously think they need to replicate => and call bar as bar(baz=>1),
and less likely to write bar(@baz=1), especially with that barrier
mnemonic. Just a hunch though.
More seriously though, if => ever makes it into Python as the new lambda
too, then bar(baz=>1) is a beginner footgun waiting to happen: you
probably meant bar(baz=1), but instead, you wrote bar(baz=lambda baz:
1). Whereas bar(@baz=1) remains a harmless SyntaxError (harmless in that
it fails early and gives you a good hint as to what's wrong).
Best,
David