19
loading...
This website collects cookies to deliver better user experience
inspect
module and getargspec
function for that but it feels a bit wrong and bloated. So let’s see how we can get the work done without the inspect
module.def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
pos1
and pos2
before the /
are positional-only arguments. You can call the following function like f(1, 2)
but not like f(a=1, b=2)
or you will get a TypeError
:>>> def f(a, b, /): pass
...
>>> f(1, 2)
>>> f(a=1, b=2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f() got some positional-only arguments passed as keyword arguments: 'a, b'
kwd1
and kwd2
after the *
are keyword-only arguments. You can call the following function like f(a=1, b=2)
and f(b=2, a=1)
but not like f(1, 2)
or you will get a TypeError
:>>> def f(*, a, b): pass
...
>>> f(a=1, b=2)
>>> f(b=2, a=1)
>>> f(1, 2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f() takes 0 positional arguments but 2 were given
f(a=1, b=2)
or f(b=2, a=1)
or f(1, 2)
will work:>>> def f(a, b): pass
...
>>> f(a=1, b=2)
>>> f(b=2, a=1)
>>> f(1, 2)
>>> def f(pos1, pos2=2, /, pos_or_kwd=3, *, kwd=4, kwd2=5, kwd3=6): pass
...
__code__
there are three interesting attributes:>>> f. __code__.co_argcount
3
>>> f. __code__.co_kwonlyargcount
3
>>> f. __code__.co_posonlyargcount
2
__code__.co_argcount
will hold the value 3
which is the number of arguments not including keyword-only arguments__code__.co_kwonlyargcount
will hold the value 3
which is the number of keyword-only arguments__code__.co_posonlyargcount
will hold the value 2
which is the number of positional-only arguments.__code__.co_argcount
and __code__.co_kwonlyargcount
. However, __code__.co_posonlyargcount
is already included in __code__.co_argcount
.__code__.co_varnames
. It contains both the function arguments and local variables and we have to take only the first elements matching the argument count:>>> f. __code__.co_varnames[:f. __code__.co_argcount+f. __code__.co_kwonlyargcount]
('pos1', 'pos2', 'pos_or_kwd', 'kwd', 'kwd2', 'kwd3')
>>> f. __defaults__
(2, 3)
>>> f. __kwdefaults__
{'kwd': 4, 'kwd2': 5, 'kwd3': 6}
__defaults__
contains a tuple
with default values or None
if there are none. It has the values for all except keyword-only arguments.__kwdefaults__
contains a dict
with the default values of keyword-only arguments.__code__.co_argcount
) is 3 but the __defaults__
contains just 2 default values. Since default arguments must follow non-default arguments, to match defaults with the actual argument names we can do something like this:>>> dict(zip(
... f. __code__.co_varnames[: f. __code__.co_argcount][-len(f. __defaults__ ) :],
... f. __defaults__ ,
... ))
{'pos2': 2, 'pos_or_kwd': 3}
*args
and **kwargs
for arbitrary positional and keyword arguments? Those are special:>>> def f(*args, **kwargs): pass
...
>>> f. __code__.co_argcount
0
>>> bin(f. __code__.co_flags)
'0b1001111'
__code__.co_argcount
) and it will be 0
. Instead, the fact that the function accepts arbitrary arguments is reflected as bits inside flags (__code__.co_flags
). The third bit (or 4) indicates that the function accepts arbitrary positional arguments and the fourth bit (or 8) indicates that the function accepts arbitrary keyword arguments.args
and kwargs
, it is just a naming convention. The real argument names can be found in __code__.co_varnames
but after all function arguments:>>> def f(pos1, pos2=2, /, pos_or_kwd=3, *args, kwd=4, kwd2=5, kwd3=6, **kwargs): pass
...
>>> f. __code__.co_varnames
('pos1', 'pos2', 'pos_or_kwd', 'kwd', 'kwd2', 'kwd3', 'args', 'kwargs')
inspect
module with a few lines of direct pybind11
C++ code:auto &&func = server.attr(svcinfo->name);
auto &&code = func.attr(" __code__");
long argcount = (code.attr("co_argcount") + code.attr("co_kwonlyargcount"))
.cast<py::int_>();
auto &&args = code.attr("co_varnames")[py::slice(0, argcount, 1)];