On Tue, Dec 07, 2021 at 12:53:39AM +1100, Steven D'Aprano wrote:
If we can't execute the expression without the context existing, we make it exist. Details to follow in another post.
Here is some handwavy pseudo-code for setting up the context prior to calling a function "func":
code = func.__code__ locals = [NIL pointer for _ in range(code.co_nlocals)] cells = [cell() for _ in range(len(code.co_cellvars))] assign args, kwargs, early defaults to locals # make a frame frame = Frame() frame.f_back = current frame frame.f_lasti = 0 frame.f_locals = locals + cells + list(func.__closure__) frame.f_code = code
and then the interpreter does its funky thang with the frame and the function is executed.
But here's the thing... there is nothing that says that the f_code has to have come from func.__code__. It just needs to be code that expects the same environment (locals, cell, etc) as the frame is set up for.
So here's how we could run the late bound default expression alone:
1. Ensure that the default expression code object has the same environment (locals, cells etc) as the function that owns it; this could be a copy, or it could be an reference back to the owner.
2. Set up the frame, as above, for the function that owns the expression.
3. But instead of setting f_code to the owner's code object, we set it to the default expression's code object. They share the same environment (cells, etc) so that's safe.
4. And then just run the function.
And to run the owner function:
1. Set up the frame.
2. Run each of the default expressions as above.
3. Then run the owner function.
Obviously this is very handwavy. But I am confident that it demonstrates that the idea of factoring default expressions out into their own code objects is not "impossible".