Let\'s imagine I have a dict :
d = {\'a\': 3, \'b\':4}
I want to create a function f that does the exact same thing than this function : >
Posting this as an answer because it would be too long for a comment.
Be careful with this answer. If you try
@kwargs_decorator(a='a', b='b')
def f(x, a, b):
print(f'x = {x}')
print(f'a = {a}')
print(f'b = {b}')
f(1, 2)
it will issue an error:
TypeError: f() got multiple values for argument 'a'
because you are defining a
as a positional argument (equal to 2).
I implemented a workaround, even though I'm not sure if this is the best solution:
def default_kwargs(**default):
from functools import wraps
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
from inspect import getfullargspec
f_args = getfullargspec(f)[0]
used_args = f_args[:len(args)]
final_kwargs = {
key: value
for key, value in {**default, **kwargs}.items()
if key not in used_args
}
return f(*args, **final_kwargs)
return wrapper
return decorator
In this solution, f_args
is a list containing the names of all named positional arguments of f
. Then used_args
is the list of all parameters that have effectively been passed as positional arguments. Therefore final_kwargs
is defined almost exactly like before, except that it checks if the argument (in the case above, a
) was already passed as a positional argument.
For instance, this solution works beautifully with functions such as the following.
@default_kwargs(a='a', b='b', d='d')
def f(x, a, b, *args, c='c', d='not d', **kwargs):
print(f'x = {x}')
print(f'a = {a}')
print(f'b = {b}')
for idx, arg in enumerate(args):
print(f'arg{idx} = {arg}')
print(f'c = {c}')
for key, value in kwargs.items():
print(f'{key} = {value}')
f(1)
f(1, 2)
f(1, b=3)
f(1, 2, 3, 4)
f(1, 2, 3, 4, 5, c=6, g=7)
Note also that the default values passed in default_kwargs
have higher precedence than the ones defined in f
. For example, the default value for d
in this case is actually 'd'
(defined in default_kwargs
), and not 'not d'
(defined in f
).