One of the motives for PEP 671 is that the signature of a function fn, and
hence the associated help(fn) is sometimes opaque regarding default values.
I won't repeat the excellent examples already given.
In the current implementation default values are handled outside the
compiled code of the function, which is available at fn.__code__. Instead
they are stored in 'metadata' associated with the function.
Here's one way to see this.
from inspect import signature as sig
def fn(a, b=1, c=2): return a, b, c
sig(fn) # Gives <Signature (a, b=1, c=2)>
fn.__defaults__ = ('hi', 'there')
sig(fn) # Gives <Signature (a, b='hi', c='there')>
We can also change the __code__ object, but care is needed here.
def gn(): return (1, 2, 3)
fn.__code__ = gn.__code__
fn() # Gives (1, 2, 3).
sig(fn) # Gives <Signature ()>
fn.__defaults__ # Gives ('hi', 'there')
The signature of fn, together with the arguments actually supplied, is used
to initialise the code frame which is put on the top of the stack, and in
which fn.__code__ executes.
I suggest that an implementation which provides additional flexibility in
the manner in which the code frame is initialised would be less invasive.
Necessarily, PEP 671 allows programmer supplied code to be used in the
'initialisation phase'. The previous attempts place that code in
I suggest that the implementation adds a new attribute fn.__wibble__ to fn,
which can either be None or a code object. And if fn.__wibble__ is not
None, then it is used to initialise the code frame in which fn.__code__
executes. It would as before take as input the arguments actually supplied
by the user.
I stop here, saying nothing for now about two important questions. First,
what is the programmer syntax for creating such a 'two-part' function fn.
Second, what does the user see as a result of help(fn). Or in other words,
how to extend the semantics of inspect.signature.
with kind regards