Is there a way to determine which arguments were passed by keyword from inside the function?
In trying to assess default values of keyword parameters, yes there are options:
Code
Option 1 - locals()
def f(a, b=1, c="1"):
print(locals())
f(0)
# {'c': '1', 'b': 1, 'a': 0}
Option 2 - Partial Type Hints*
def g(a, b:int=1, c:str="1"):
pass
keys = g.__annotations__
values = g.__defaults__
dict(zip(keys, values))
# {'b': 1, 'c': '1'}
Option 3 - Full Type Hints*
def h(a:float, b:int=1, c:str="1") -> int:
return 0
keys = reversed(list(filter(lambda x: x != "return", h.__annotations__)))
values = reversed(h.__defaults__)
{k: v for k, v in zip(keys, values) if k != "return"}
# {'c': '1', 'b': 1}
Note: None of these options are particularly Pythonic, but they demonstrate potential.
Details
locals()
depends on the function call. The results should be default values, but they change with values passed into the call, e.g. f(0)
vs. f(0 2, 3)
- "Partial" type hints mean only keyword parameters are annotated. Adding any other annotations will not work with this naive approach.
- "Full" or complete type hints may include other parameters. Since a
"return"
annotation is optional, we filter it from our keys. Furthermore, we iterate backwards to trim potential positional parameters with zip()
.
*These options depend on type hints and key insertion order preservation (Python 3.6+). They only give the default values and do not change with function call values. Type hints are optional right now in Python, and thus should be used with caution in production code.
Suggestion
I would only use the latter approaches to debug or quickly inspect a function's signature. In fact, given keyword-only arguments (right of the *
), one can use inspect.getargspec()
to capture the kwonlydefaults
dict.
def i(a, *, b=1, c="1"):
pass
spec = inspect.getfullargspec(i)
spec
# FullArgSpec(args=['a'], varargs=None, varkw=None,
# defaults=None, kwonlyargs=['b', 'c'],
# kwonlydefaults={'b': 1, 'c': '1'}, annotations={})
spec.kwonlydefaults
# {'b': 1, 'c': '1'}
Otherwise, combine some of the mentioned techniques with the args
and defaults
attributes of FullArgSpec
:
def get_keywords(func):
"""Return a dict of (reversed) keyword arguments from a function."""
spec = inspect.getfullargspec(func)
keys = reversed(spec.args)
values = reversed(spec.defaults)
return {k: v for k, v in zip(keys, values)}
get_keywords(f)
# {'c': '1', 'b': 1}
where the FullArgSpec
from the regular function f
would show:
spec = inspect.getfullargspec(f)
spec
# FullArgSpec(args=['a', 'b', 'c'], varargs=None, varkw=None,
# defaults=(1, '1'), kwonlyargs=[],
# kwonlydefaults=None, annotations={})