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.