On Wed, Dec 8, 2021 at 11:28 PM Chris Angelico <rosuav@gmail.com> wrote:
On Thu, Dec 9, 2021 at 3:15 PM Jonathan Goble <jcgoble3@gmail.com> wrote:
My preferences to resolve this are, in order:
1. Introduce `from __future__ import late_default`. When present, argument defaults in that file are late bound instead of early bound. After a suitable deprecation period, make the future statement the default behavior. Then Python will comply with best practices demonstrated by Steven's language review. I have not done any analysis, but I believe based on intuition that any breakage in libraries and scripts stemming from this would be relatively easy to fix, and most existing code should just work (in particular, the common existing usage of a sentinel as a default with an `is None` or `is sentinel` check in the body would not break and could be migrated to the new behavior at leisure). If true, it would result in minimal fuss for maximum benefit.
IMO this is strictly worse than supporting both alternatives with syntactic differences. The language still needs to support both, programmers still need to comprehend both, but instead of being able to distinguish "def f(x=[]):" from "def f(x=>[]):", you have to go look at the top of the file to see which way around it is. To the extent that the distinction needs to be visible, it needs to be visible at the function's definition, not at the top of the file.
Is it really worse? Yes, it's a burden during the transition period, but the burden (both of managing both behaviors, and of grokking early-bound mutable defaults) eventually goes away after a couple versions and adoption time. After that, only one needs to be taught and understood. In contrast, adding a syntax for late binding while keeping early binding means you have to teach and comprehend both behaviors forever. I'd much, much rather have a temporary burden than a permanent burden.
2. If a future statement and behavior change is deemed too disruptive, then keep early binding, do not introduce late binding, and introduce a new use for the keyword `pass` to represent an absent argument. Under this idea, `pass` would be accepted as an expression in the following three contexts, and exactly nowhere else: a) as a complete argument to a call, b) as a complete "value" for an argument default, and c) in the expressions `x is pass` and `x is not pass`, but only when both `x` is a parameter to the immediately enclosing function and the default value of that parameter is `pass`. This way, `pass` acts as a sentinel that isn't a valid value in any other context, which would solve the issue of when `None` is a valid value.
This is a good idea that desperately needs good syntax. I don't like "pass" used in this way. It's perfectly implementable but only if someone can figure out how to write it.
(I'd define it as "the default is for the variable to be unbound" and "if the variable is unbound". That makes very good sense and would work within the language.)
Unfortunately this still has several of the problems that argument defaults are supposed to solve. It means that you can mark a parameter as optional, but you get no information about what it would be if omitted. That's just as bad as the current sentinel option, with the only advantage being that there's no sentinel.
Option 2 might actually make a good extension beyond PEP 671, but it's not a replacement for it.
I never said it was a replacement. On the contrary, I explicitly said in the first sentence of Option 2 to "do not introduce late binding". Period. I am firmly against having both behaviors baked into the language forever. If we cannot change the default behavior to late binding via a future statement and a deprecation period, then we should not introduce late binding at all. Python should have one permanent behavior, and should not support both at once unless as part of a temporary transition to a new default behavior. In other words, the status quo with all of its warts is highly preferable to me over PEP 671, however it's spelled. I stand as follows: My Option 1 (future statement, deprecation of early binding, and permanent switch to late binding): +1 My Option 2 (syntax for an unbound argument): +0.4 Status quo (no changes at all): +0.3 PEP 671 or any other proposal to introduce a different spelling for late binding (thereby supporting both at once forever): -1