
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