I had the impression that you could use the inspect.Signature function to retrieve and distinguish between positional and keyword arguments. However this does not seem to be the case:
def foo(a,b,c, t=3, q=5):
print(a,b,c,t,q)
[(u, u.kind) for u in i.signature(foo).parameters.values()]
[(<Parameter "a">, <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>),
(<Parameter "b">, <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>),
(<Parameter "c">, <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>),
(<Parameter "t=3">, <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>),
(<Parameter "q=5">, <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>)]
So, as it is, seems the kind attribute is pretty useless to distinguish between positional and keyword arguments
So the question here is:
How do I distinguish between arguments a,b,c and arguments t,q so that if I were to invoke foo:
#build args from signature with args = [a,b,c]
#build kwargs from signature with kwargs = {'t': t,'q': q}
foo(*args, **kwargs)
Unless you explicitly define function parameters as being POSITIONAL_ONLY or KEYWORD_ONLY using the syntax below, the default behavior is for all parameters to be POSITIONAL_OR_KEYWORD. A difference exists between the behavior of Python 3.8+ and previous versions of Python.
In Python 3.8+ whether an argument is positional only or keyword only can be specified using the / and * syntax, respectively. As an example:
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
----------- ---------- ----------
| | |
| Positional or |
| keyword Keyword only
Positional only
Everything before / is positional only; everything after * is keyword only. Note that order matters – / must come before *. Also, if you don't explicitly specify POSITIONAL_ONLY or KEYWORD_ONLY using this syntax, all arguments default to the POSITIONAL_OR_KEYWORD value for the kind attribute.
This is due to a change that was made in Python 3.8. The behavior of the / syntax was specified in PEP 570 (following PEP 457). In code:
>>> import inspect
# Positional or keyword (default behavior)
>>> def meow (a, b, c = 0, d = 1):
... return (a * b + c) * d
>>> {p.name: p.kind for p in inspect.signature(meow).parameters.values()}
{'a': <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>,
'b': <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>,
'c': <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>,
'd': <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>}
# Positional only, positional or keyword, keyword only
>>> def meow (a, /, b, c = 0, *, d = 1):
... return (a * b + c) * d
>>> {p.name: p.kind for p in inspect.signature(meow).parameters.values()}
{'a': <_ParameterKind.POSITIONAL_ONLY: 1>,
'b': <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>,
'c': <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>,
'd': <_ParameterKind.KEYWORD_ONLY: 1>}
Prior to PEP 570, the / syntax did not exist but the * syntax did (haven't been able to find the exact PEP when it was introduced); trying the / in 3.7 raises a syntax error:
# Python 3.7 - we get an error if use the `/` syntax
>>> def meow (a, /, b, c = 0, *, d = 1):
File "<stdin>", line 1
def meow (a, /, b, c = 0, *, d = 1):
# If we omit the `/` but keep the `*`, it works
>>> def meow (a, b, c = 0, *, d = 1):
... return (a * b + c) * d
>>> {p.name: p.kind for p in inspect.signature(meow).parameters.values()}
{'a': <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>,
'b': <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>,
'c': <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>,
'd': <_ParameterKind.KEYWORD_ONLY: 1>}
Aside from the PEPs, I also found this quick summary helpful to understanding the behavior.
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