Second version of the POC implementation in response to feedback. On Fri, Oct 29, 2021 at 7:17 PM Chris Angelico <rosuav@gmail.com> wrote:
https://github.com/Rosuav/cpython/tree/pep-671
So uhh... anyone who knows about the internals of CPython and wants to join me on this, I would *really* appreciate coauthors!
Still interested in coauthors who know CPython internals. That hasn't changed.
The implementation ended up a lot more invasive than I originally planned. Some of that is inherent to the problem, but other parts might be able to be done more cleanly. The way I've done it:
It's still more invasive than intended :)
* Argument defaults (either in __defaults__ or __kwdefaults__) are now tuples of (desc, value) or (desc,) for early-bound and late-bound respectively
This is the part that's changed. Instead of tuples in those slots, there are now pairs of slots __defaults__ with __defaults_extra__, and __kwdefaults__ with __kwdefaults_extra__. For every late-bound default, there will be Ellipsis as a placeholder value, and then a disambiguating marker in the extras tuple/dict; None means that it's actually the value Ellipsis after all, or a string indicates that it's a late-bound default.
* Early-bound defaults get mapped as normal. Late-bound defaults are left unbound at time of function call. * For each late-bound default as of the 'def' statement, a check is coded: if the local is unbound, set it based on the given expression.
This part of the implementation is still the same.
This means that it's possible to replace an early-bound default with a late-bound, but instead of actually evaluating the expression, it just leaves it unbound:
def f(x=1): print(x) ... f.__defaults__ ((None, 1),) f.__defaults__ = ((None,),) f() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 1, in f UnboundLocalError: cannot access local variable 'x' where it is not associated with a value
These shenanigans can still be done, but instead of changing __defaults__, it would be done by changing __defaults_extra__:
def f(x=1): print(x) ... f.__defaults__ (1,) f.__defaults_extra__ = ('',) f.__defaults__ = (...,) f() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 1, in f UnboundLocalError: cannot access local variable 'x' where it is not associated with a value
So far unimplemented is the description of the argument default. My plan is for early-bound defaults to have None there (as they currently do), but late-bound ones get the source code. (In theory, it may be of value to retain the source code for earlies too, which would allow hex or octal integer literals to show up in help() as such, rather than showing the (decimal) repr of the resulting value.) Anyone got good pointers on how to do this, or is that likely to be impractical?
Much the same here. Anyone know of a good way to get source code sections during compilation? If not, I'll dig around when I get a moment. One difference is that early-bound defaults probably won't get descriptions, although it's certainly possible.
Feel free to criticize my code. As you'll see from the commit messages in that branch, I have no idea what I'm doing here :)
Believe you me, this part hasn't changed a bit.... :) So there's really only been one notable change to the implementation. Hopefully this should ensure backward compatibility. There are a few failing tests and I'm going through them now. ChrisA