On Fri, Oct 29, 2021 at 11:52 PM Steven D'Aprano <steve@pearwood.info> wrote:
Except that that's still backward-incompatible, since None is a very common value.
How is it backwards incompatible? Any tool that looks at __defaults__ finds *exactly* what was there before: a tuple of default values, not a tuple of tuples (desc, value) or (value,) as in your implementation.
It's fundamentally impossible to make this change without some measure of backward incompatibility. Is it such an advantage to be almost the same? It means that tools will be correct often enough that people might not even notice a problem, and then they subtly give a completely false value. With my scheme, they *always* give a clear and obviously wrong value, and it's easy to see what has to be done. Python doesn't, as a general rule, opt for things that encourage subtle data-dependent bugs.
With your implementation, it will always lie. Always. Every single time, no exceptions: it will report that the default value is a tuple (desc, value), which is wrong.
Yes, it will clearly need to be updated for the new version. Instead of being almost correct, and then flat-out lying in situations where it should learn a better way.
With mine, it will be correct nearly always, especially if we use NotImplemented as the sentinel instead of None. And the cases that it gets wrong will only be the ones that use late-binding. It will never get an early-bound default wrong.
I disagree that "correct nearly always" is a good thing.
which is basically the same as I have, only all in a single attribute.
Right. And by combining them into a single attribute, you break backwards compatibility. I think unnecessarily, at the cost of more complexity.
What if, in the future, a third type of optional argument is added - such as "leave it unbound, no default given"? How would your scheme handle this? Mine handles it just fine: give a new kind of value in __defaults__. (Maybe None would work for that.)
Remember that __defaults__ is writable. What happens if somebody sticks a non-tuple into the __defaults__? Or a tuple with more than two items?
func.__defaults__ = ((desc, value), (descr, value), 999, (1, 2, 3))
Writing to it goes through a checker already. You already can't write func.__defaults__ = "foo".
So under your scheme, the interpreter cannot trust that the defaults are tuples that can be interpreted as (desc, value).
Technically that's true in my current implementation, because I haven't written the proper checks, but the interpreter is in full control of what goes into that attribute.
Right, but you said that the early bound defaults **currently** have a None there. They don't. The current status quo of early bound defaults is that they are set to the actual default value, not a tuple with None in it. Obviously you know that. So that's why I'm asking, what have I misunderstood?
You've misunderstood what I meant by "currently". Ignore it. That was just one of the open issues.
even do up their own from scratch, I would welcome it. It's much easier to poke holes in an implementation than to actually write one that is perfect.
I've suggested an implementation that, I think, will be less complex and backwards compatible. I don't know if it will be faster. I expect in the common case of early binding, it will be, but what do I know about C?
I asked for someone to WRITE an implementation, not suggest one. :) It's easy to poke holes in someone else's plans. Much harder to actually write something. Go ahead and put some code behind your words, and then we can test them both.
And fundamentally, there WILL be behavioural changes here, so I'm not hugely bothered by the fact that the inspect module needs to change for this.
It's not just the inspect module. __defaults__ is public[1]. Anyone and everyone can read it and write it. Your implementation is breaking backwards compatibility, and I believe you don't need to.
[1] Whether it is *officially* public or not, it is de facto public.
What does "de facto public" mean, and does the language guarantee that its format will never change? And, once again, you're offering something that is often correct and sometimes a flat-out lie, where I'm offering something that adds a clear and obvious structure. If you naively assume that everything in __defaults__ is a two-element tuple (which is the case for early-bound defaults), you'll get an obvious IndexError when you hit a late-bound default, so even a basic adjustment will still be safe. By your method, unless something is aware of late defaults, it will subtly get things wrong. ChrisA