Python 3.6
I'm attempting to create a decorator that automatically assigns the string of the argument as the default value.
such as:
def example(one='one', two='two', three='three'):
pass
would be equivalent to:
@DefaultArguments
def example(one, two, three):
pass
Here is my attempt (doesn't work.. yet..) DefaultArguments:
from inspect import Parameter, Signature, signature
class DefaultArguments(object):
@staticmethod
def default_signature(signature):
def default(param):
if param.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.POSITIONAL_ONLY):
return param.replace(default=param.name)
else:
return param
return Signature([default(param) for param in signature.parameters.values()])
def __init__(self, func):
self.func = func
self.sig = self.default_signature(signature(func))
def __call__(self, *args, **kwargs):
arguments = self.sig.bind(*args, **kwargs)
return self.func(arguments)
The staticmethod default_signature creates the desired signature for the function, but I'm having difficulty assigning the new signature to the function. I'm trying to use Signature.bind I've read the docs but i'm missing something.
EDIT
Incorporating Ashwini Chaudhary's answer:
from inspect import Parameter, Signature, signature
class DefaultArguments(object):
@staticmethod
def default_signature(signature):
def default(param):
if param.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.POSITIONAL_ONLY):
return param.replace(default=param.name)
else:
return param
return Signature([default(param) for param in signature.parameters.values()])
def __init__(self, func):
self.func = func
self.sig = self.default_signature(signature(func))
print(self.sig)
def __call__(self, *args, **kwargs):
ba = self.sig.bind(*args, **kwargs)
ba.apply_defaults()
return self.func(*ba.args, **ba.kwargs)
解决方案
After binding the args and keywords with the signature you need to call apply_defaults on the BoundArguments instance to set the default values for missing arguments.
Also the function call will be invoked using BoundArguments's args and kwargs properties.
def __call__(self, *args, **kwargs):
ba = self.sig.bind(*args, **kwargs)
ba.apply_defaults()
return self.func(*ba.args, **ba.kwargs)
Demo:
>>> @DefaultArguments
... def example(one, two, three):
... print(one, two, three)
...
>>> example()
one two three
>>> example('spam')
spam two three
>>> example(one='spam', three='eggs')
spam two eggs
A functional version of your code that also updates the signature of the decorated function:
from functools import wraps
from inspect import Parameter, Signature, signature
def default_arguments(func):
def default(param):
if param.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.POSITIONAL_ONLY):
param = param.replace(default=param.name)
return param
sig = Signature([default(param) for param in signature(func).parameters.values()])
@wraps(func)
def wrapper(*args, **kwargs):
ba = sig.bind(*args, **kwargs)
ba.apply_defaults()
return func(*ba.args, **ba.kwargs)
wrapper.__signature__ = sig
return wrapper
Demo:
>>> from inspect import getfullargspec
>>> @default_arguments
... def example(one, two, three):
... print(one, two, three)
...
>>> getfullargspec(example)
FullArgSpec(
args=['one', 'two', 'three'],
varargs=None,
varkw=None,
defaults=('one', 'two', 'three'),
kwonlyargs=[], kwonlydefaults=None, annotations={}
)