I addressed some points in @Menglong Li's answer and simplified the code.
import inspect
import functools
def ignore_unmatched_kwargs(f):
"""Make function ignore unmatched kwargs.
If the function already has the catch all **kwargs, do nothing.
"""
if contains_var_kwarg(f):
return f
@functools.wraps(f)
def inner(*args, **kwargs):
filtered_kwargs = {
key: value
for key, value in kwargs.items()
if is_kwarg_of(key, f)
}
return f(*args, **filtered_kwargs)
return inner
def contains_var_kwarg(f):
return any(
param.kind == inspect.Parameter.VAR_KEYWORD
for param in inspect.signature(f).parameters.values()
)
def is_kwarg_of(key, f):
param = inspect.signature(f).parameters.get(key, False)
return param and (
param.kind is inspect.Parameter.KEYWORD_ONLY or
param.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD
)
Here are some test cases:
@ignore_unmatched_kwargs
def positional_or_keywords(x, y):
return x, y
@ignore_unmatched_kwargs
def keyword_with_default(x, y, z = True):
return x, y, z
@ignore_unmatched_kwargs
def variable_length(x, y, *args, **kwargs):
return x, y, args,kwargs
@ignore_unmatched_kwargs
def keyword_only(x, *, y):
return x, y
# these test should run without error
print(
positional_or_keywords(x = 3, y = 5, z = 10),
positional_or_keywords(3, y = 5),
positional_or_keywords(3, 5),
positional_or_keywords(3, 5, z = 10),
keyword_with_default(2, 2),
keyword_with_default(2, 2, z = False),
keyword_with_default(2, 2, False),
variable_length(2, 3, 5, 6, z = 3),
keyword_only(1, y = 3),
sep='\n'
)
# these test should raise an error
print(
#positional_or_keywords(3, 5, 6, 4),
#keyword_with_default(2, 2, 3, z = False),
#keyword_only(1, 2),
sep = '\n'
)