You could write a (somewhat convoluted) decorator to provide the arguments and their default value as signature to the function:
import inspect
def signature(params):
arglist = ",".join(f"{k}={v.__repr__()}" for k,v in params.items())
def addSignature(f):
names = dict()
code = inspect.getsource(f).split("):\n")[-1]
exec(f"def f({arglist}):\n"+code,names)
return names['f']
return addSignature
usage:
@signature(dict(zip("abcdefghijklmnopqrstuvwxyz",range(1,27))))
def myFunc():
print(locals())
myFunc(4,5) # all arguments are available as local variables
{'a': 4, 'b': 5, 'c': 3, 'd': 4, 'e': 5, 'f': 6, 'g': 7, 'h': 8, 'i': 9,
'j': 10, 'k': 11, 'l': 12, 'm': 13, 'n': 14, 'o': 15, 'p': 16, 'q': 17,
'r': 18, 's': 19, 't': 20, 'u': 21, 'v': 22, 'w': 23, 'x': 24, 'y': 25,
'z': 26}
inspect.signature(myFunc)
<Signature (a=1, b=2, c=3, d=4, e=5, f=6, g=7, h=8, i=9, j=10, k=11,
l=12, m=13, n=14, o=15, p=16, q=17, r=18, s=19, t=20, u=21, v=22, w=23,
x=24, y=25, z=26)>
The decorator essentially rewrites the function with the signature assembled from string representations of the dictionary key/values.