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)
This seems to work:
import inspect
def default_args(func):
argspec = inspect.getfullargspec(func)
@functools.wraps(func)
def wrapper(*args, **kwargs):
unpassed_positional_args = argspec.args[len(args):]
kwargs.update((a, a) for a in unpassed_positional_args if a not in kwargs)
return func(*args, **kwargs)
return wrapper
It relies on the fact that you can pass positional arguments by keyword in python. e.g. if you have a function:
def foo(a, b):
...
You're completely in your rights to call it as:
foo(b=1, a=2)
My solution figures out how many positional arguments you've passed and uses that to figure out which positional arguments weren't passed. I then add those positional argument names to the kwargs
dict instead.
And the cool thing here is that if someone needs this for python2.x, they only need to change getfullargspec
to getargspec
and it should work OK.
Comparing my solution with Ashwini's excellent explanation shows that the simple decorator is approximately 10x faster than messing around with Signature objects:
@default_args
def foo(a, b, c):
pass
@DefaultArguments
def bar(a, b, c):
pass
@default_arguments
def qux(a, b, c):
pass
import timeit
print(timeit.timeit('foo()', 'from __main__ import foo')) # 1.72s
print(timeit.timeit('bar()', 'from __main__ import bar')) # 17.4s
print(timeit.timeit('qux()', 'from __main__ import qux')) # 17.6
His solution actually updates the __signature__
of the function (which is really nice). In principle, you could take the Signature
creation logic and add that to my solution to update the __signature__
but keep the argspec
style logic for the actual computation...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With