How does that have anyhting to do with mutable default arguments though? And it doesn’t help much, since None would still be allowed.
Actually, multiple dispatch avoids using None as the default value:
@singledispatch
def f():
return f([])
@f.register(list)
def f(x: list[Any]):
return do_stuff(x)
The problem, as I mentioned before, is that the number of declarations required could be 2**n for n default arguments.
Also, if decorators are to be involved, there have been simpler solutions proposed.
I think this thread may have played out its useful lifecycle.
I’d love to take a serious look at the past discussion of PEP 671 and see if the concerns around it could be resolved.
That seems to me like the most productive way to attempt to proceed, but several people who participated in those discussions have indicated and warned that it’s no easy task.
Discussion of a defer keyword is really just discussion of an alternative syntax for 671. So perhaps a dedicated thread for alternatives to the 671 syntax would be appropriate. But starting that without reading the past discussion seems unwise, which is why I’m not doing that myself right now.
I’m not the Off Topic Police (anymore! I got too old and slow to chase those really fast topics), so keep chatting here if you like. I’m just pointing out that this thread is currently a long way from any serious proposal for late binding defaults.
And before it gets thrown in my face as some kind of counterpoint, Chris is right. That late package has no business calling itself late binding if it copies values. Name squat on a valuable bit of land in pypi if you want, but it doesn’t help the community get the feature we’d actually like.
I’d love that too, but history has shown that every time the topic is brought up again, the discussion rapidly devolves into unfounded assertions that a generalized deferred expression would be better and therefore PEP 671 should be changed into that.
Every. Single. Time.
No. It is not.
I apologize – I promise I have been reading carefully – I mean “the subset of proposals which want defer in function signatures to mean late binding, without reckoning with the distinction between late binding and deferred evaluation”. I think that several of those, like x=1, defer y=[], are just choosing some new syntax vs =>.
I can’t find that anyone else suggested using from __future__. The deferred evaluation behavior could be enabled by a future import. It would have to be unlike other future options in that we would need to give a long (or even indefinite) period for people to opt-in to the new behavior.
I think no one suggested this because no one has agreed on what to change in the first place.
Maybe, but that subset aren’t well defined proposals, so they’re not really discussion material as alternative syntaxes.
Part of the reason that nobody’s seriously suggested a future import for this is that it’s something that is better to introduce in parallel. Late binding would be less efficient than early binding, and doesn’t work as well with certain types of introspection, so it would be best to allow a function to be written with a mix of early and late bound defaults as needed. Since there’s good reason to keep early binding around forever, it’s better to devise a new syntax for late binding, thus ensuring that existing code retains its semantics. For example:
def func(stuff=>[], mode=constants.MODE_NORMAL): ...
would have one late-bound argument and one early-bound, with the value of constants.MODE_NORMAL being retained, but the code of constructing a new list being prepended to the function.
While it would be reasonable and sane to have a language with nothing but late-bound defaults, changing Python to this would likely cause extremely subtle problems with people’s code (since the vast majority of functions will behave identically in both forms), so as backward-compatibility breaches go, this would be a pretty nasty one. At the point at which the future directive becomes the default, there would be bizarre phenomena where the behaviour of a function changes very slightly across Python versions, with no apparent reason.
I started this thread to seek a solution for the semantics of mutable default arguments, and the conversation somehow got sidetracked into general late evaluation of expressions and PEP 671.
The Late library is one solution to the stated problem with simple semantics, including deferred evaluation through support for generators.
Getting rid of the | None = None typing is enough for me:
def f(x: list[Any] | None = None) -> list[Any]:
Copying was the simple solution, considering there’s no access to original expressions in user/decorator space. The interpreter, OTOH, could save the AST for the given expression through the new, elusive, syntax no one seems to agree about.
The fact is that Late works, and allows for sensible typing annotations.
There’s contest in the definition of Late Binding in Wikipedia, but the implementation of Late is consistent with what it says there: run-time versus compile-time resolution.
Other names (including “wrap”) were taken. Sorry about that! ![]()
It’s fine for copying to be the solution. But if copying is the solution, why not call it copying? Why pretend that it’s late binding?
Why not, oh I dunno, copydefaults or something? Because that’s what it does. There are LOTS of possible names you could use, instead of mudding the already-muddy waters even more. Why pick a completely false name?
See Late Binding in Wikipedia.
… yes. I’m trying to find any example in that page that talks about copying mutable objects. Your point?
I apologize.
My point is that the “late” in “late binding” is about resolution at run time instead of compile time.
If you don’t look at the implementation of Late (or you imagine I found some magic to capture expression ASTs for later evaluation), the semantics of the library would be the same.
It matters a lot to me that alike semantics could be implemented by core developers with little effort (except, of course, agreeing on the syntax [something that reminds me about the endless thread around :=]).
Something else that I find interesting is that currently there are ways to have sensible, no-surprise late evaluation of expressions with everything around yield. If you take a look at the parsers generated by TatSu, you’ll see that there’s a lot of logic executing in contexts off the current program line.
Comments from a different channel.
At that point Late offered support for generators, not functions. Also constructors for nested structures were overlooked. There’s support for all of that now.
I did this little experiment before writing Late, and found it fascinating:
>>> x = range(10)
>>> y = (f'a{v}' for v in x)
>>> z = list(x)
>>> x = None
>>> list(y)
['a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9']
Failed to stay productive and on topic. Resulted in frustration rather than consensus towards a specific idea.