Maybe you could make a function decorator that looked for a special type of default value indicating how to make the default value each time. Similar to `attr.Factory` [1] for `attr.dataclass` annotations.
If you make the functions to supply late-bound defaults arguments to the decorator, its pretty doable; here’s what I just threw together.
from inspect import signature, Parameter
def with_deferred_defaults(*args, **kwargs):
def decorate(fun):
parameters = signature(fun).parameters
positional_parameters = [parameter for parameter in parameters.values() if parameter.kind in [Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD]]
defaults = list(args)
kwdefaults = kwargs
def wrapper(*args, **kwargs):
args = list(args)
if len(args) < len(defaults):
args = args + [arg() for arg in defaults[len(args):]]
new_kwargs = {k: v() for k, v in kwdefaults.items()}
new_kwargs.update(kwargs)
for i, p in enumerate(positional_parameters):
if i >= len(args):
if p.name in new_kwargs:
args.extend([None]*(1+i-len(args)))
args[i] = new_kwargs[p.name]
del new_kwargs[p.name]
elif p.default is Parameter.empty:
raise TypeError('Missing argument: {}'.format(p.name))
else:
if p.name in new_kwargs:
del new_kwargs[p.name]
return fun(*args, **new_kwargs)
return wrapper
return decorateI'd also like whatever a solution is to work well with type annotations.
I'm surprised there isn't a pep to improve the situation. (Maybe there is and I haven't found it!)