Let's talk about the choice of spelling. So far, most of the suggested
syntaxes have modified the assignment symbol `=` using either a prefix
or a suffix. I'm going to use '?' as the generic modifier. So the
parameter will look like:
# Modifier as a prefix
param?=expression
param:Type?=expression
# Modifier as a suffix
param=?expression
param:Type=?expression
One problem with these is that (depending on the symbol used), they can
be visually confused with existing or possible future features, such as:
* using a colon may be confusable with a type hint or walrus operator;
* using a greater-than may be confusable with the proposed Callable
sugar -> or lambda sugar =>
* as well as just plain old greater-than;
* using a question mark may be confusable with hypothetical
None-aware ??= assignment.
By confusable, I don't mean that a sophisticated Python programmer who
reads the code carefully with full attention to detail can't work out
what it means.
I mean that new users may be confused between (say) the walrus `:=` and
"reverse walrus" `=:`. Or the harrassed and stressed coder working at
3am while their customer on the other side of the world keep messaging
them. We don't always get to work carefully with close attention to
detail, so we should prefer syntax that is less likely to be confusable.
So far, I dislike all of those syntaxes (regardless of which symbol is
used as the modifier). They are all predicated on the idea that this is
a new sort of assignment, which I think is the wrong way to think about
it. I think that the better way to think about it is one of the
following:
1. It's not the assignment that is different, it is the expression
being bound.
2. It is not the assignment that is different, it is the parameter.
Suggestion #1 suggests that we might want a new kind of expression,
which for lack of a better term I'm going to call a thunk (the term is
stolen from Algol). Thunks represent a unit of delayed evaluation, and
if they are worth doing, they are worth doing anywhere, not just in
parameter defaults. So this is a much bigger idea, and a lot more
pie-in-the-sky as it relies on thunks being plausible in Python's
evaluation model, so I'm not going to talk about #1 here.
Suggestion #2 is, I will argue, the most natural way to think about
this. It is the parameter that differs: some parameters use early
binding, and some use late binding. Binding is binding, regardless of
when it is performed. When we do late binding manually, we don't do
this:
if param is None:
param ?= expression # Look, it's LATE BINDING assignment!!!
So we shouldn't modify the assignment operator. It's still a binding.
What we want to do is tell the compiler that *this parameter* is
special, and so the assignment needs to be delayed to function call time
rather than function build time. We can do that by tagging the
parameter.
What's another term for tagging something? Decorating it. That suggests
a natural syntax:
# arg1 uses early binding, arg2 uses late binding
def function(arg1=expression, @arg2=expression):
And with type annotations:
def function(arg1:Type=expression, @arg2:Type=expression) -> Type:
I know it's not an actual decorator, but it suggests the idea that we're
decorating the parameter to use late binding instead of early.
Advantages:
- The modifer is right up front, where it is obvious.
- Doesn't look like grit on the monitor.
- It can't be confused with anything inside the type hint or
the expression.
- No matter how ludicrously confusing the annotation or expression
gets, the @ modifier still stands out and is obvious.
- Forward-compatible: even if we invent a prefix-unary @ operator
in the future, this will still work:
def function(@param:@Type=@expression)
- Likewise for postfix unary operators:
def function(@param:Type@=expression@)
Here's a trivial advantage: with the "modify the equals sign" syntax, if
you decide to copy the assignment outside of the function signature, you
are left with a syntax error:
def function(param?=expression)
# double-click on "param", drag to expression, copy and paste
param?=expression # SyntaxError
Its not a big deal, but I can see it being a minor annoyance, especially
confusing for newbies. But with a leading @ symbol, you can double-click
on the param name, drag to the expression, copy and paste, and in most
GUI editors, the @ symbol will not be selected or copied.
def function(@param=expression)
# double-click on "param", drag to expression, copy and paste
param=expression # Legal code.
(I don't know of any GUI editors that consider @ to be part of a word
when double-clicking, although I suppose there might be some.)
Disadvantages:
- Maybe "at symbol" is clunkier to talk about than "arrow operator" or
"reverse walrus"?
- Search engines aren't really great at searching for the at symbol:
https://www.google.com.au/search?q=python+what+does+%40+mean
https://duckduckgo.com/?q=python+what+does+%40+mean
DDG gives the top hit a Quora post about the at symbol, but everything
else is a miss; Google is even worse. But then any other symbol is going
to be subject to the same problem.
Looking back at the "modify the equals" syntax, it puts the important
information right there in the middle of something which could be an
extremely busy chunk of text:
param:Optional[Callable[TypeA, TypeB, Bool]]=>lambda a, b: a>lo and b>hi
Even if it is syntactically unambiguous, and not confused with anything
else, it is still not obvious. It doesn't stand out when skimming the
code. And we all sometimes just skim code.
@param:Optional[Callable[TypeA, TypeB, Bool]]=lambda a, b: a>lo and b>hi
Let's have a look at some real cases from the stdlib:
# bisect.py
def bisect_right(a, x, lo=0, @hi=len(a), *, key=None):
# calendar.py
class LocaleTextCalendar(TextCalendar):
def __init__(self, firstweekday=0, @locale=_locale.getdefaultlocale()):
# copy.py
def deepcopy(x, @memo={}, _nil=[]):
# pickle.py
class _Pickler:
def __init__(self, file, @protocol=DEFAULT_PROTOCOL, *,
fix_imports=True, buffer_callback=None):
(Note: some of these cases may be backwards-incompatible changes, if the
parameter is documented as accepting None.)
--
Steve
_______________________________________________
Python-ideas mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at
https://mail.python.org/archives/list/[email protected]/message/RQQQWH2K3BXCXCBUX5ZZBBRFYLXDN7TK/
Code of Conduct: http://python.org/psf/codeofconduct/