
I have a suggestion for the implementation. I think that Chris' current approach is to compile the late-bound defaults in the function body. So if we have a function like this: def func(a, b=early_expression, @c=late_expression): block the function code looks like this (after compilation): # Pseudocode if c is unbound: c = late_expression block (except in bytecode of course). Chris, do I have that right? If it is wrong, probably everything I say next is irrelevant. There is a strange asymmetry to the way the default for b and c are handled. For b, it is the interpreter's responsibilty to load the default value (pre-evaluated and cached) and bind it to the parameter. But for c, it is the function object's responsibility. I'd like to suggest a different approach which I expect will be more flexible, I hope won't cost too much in performance, and in my opinion much more closely matches the semantics of the feature. I think it should remain the interpreter's responsibility to set up all the parameters before entering the function, including late-bound defaults. That will have the big advantage that disassembling func will only show the code for "block", not the associated code that tests and binds late-bound defaults. (Just like currently, it doesn't show the code for binding early-bound defaults.) This suggests that each late-bound expression should be compiled into a separate code object, all of which are then squirrelled away in the function object (just as the __code__ and __defaults__ currently are). I imagine the process will be something like: * set up a new local namespace for the function call * bind arguments to parameters in that namespace * bind early-bound defaults from the function __defaults__ to parameters * (NEW) evaluate the appropriate late-bound expression code objects, running them in the local namespace, and binding their results to the parameters; * enter the function's code block. Benefits: - the function code block is smaller, since it no longer has to include the "test, evaluate, bind" for every late-bound parameter; - this may improve code locality, which is good for performance (or so I am told); - introspection tools such as dis can disassemble the body of the function independently of the late-bound parameters; - which means we can inspect the late-bound parameters independently by passing their code object to dis; - for testing, we can evaluate the expression code objects using eval (maybe?); - we may be able to include the source code to the expression in the expression's own code block, e.g. in the co_name field(?); - we may be able to replace/modify the defaults' code blocks independently of the main function __code__, e.g. for byte-code hacking, or other function object hacking. Costs: - the function object itself may be a little larger. Thoughts? -- Steve