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
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/NQCVLVYH7PEPXVK3AIFUU5Y7XXZM2KOK/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to