For the first part of your question: The thing is that (in Python 2) there isn't a complete distinction between "keyword" and "positional" arguments as such. Any argument value can be passed positionally or by keyword. It's just that if the argument doesn't have a default value, that argument must be supplied --- but it can still be passed by keyword. (In Python 3 it's possible to have true keyword-only arguments that cannot be passed positionally.)
The way to match up the arguments with their defaults, then, is to realize that the defaults can only apply to the last arguments. So if a function accepts four arguments and has two default argument values, those values must be for the third and fourth arguments, with the first two arguments having no default values. In common parlance, this is a function with two positional arguments and two keyword arguments (although, as I mentioned, this is only half-accurate, since any argument can always be passed by keyword). As the documentation says, the keywords
part of the result of getargspec
is not meant to hold the keyword arguments; it holds the name of the keyword varargs argument (e.g., **kwargs
), if there is one.
To see how to match them up, just look at the source code for inspect.formatargspec
, which does essentially this (I changed it slightly to build a list instead of a string representation):
args, varargs, varkw, defaults = inspect.getargspec(func)
result = []
if defaults:
firstdefault = len(args) - len(defaults)
for i, arg in enumerate(args):
if defaults and i >= firstdefault:
result.append((arg, defaults[i - firstdefault]))
else:
result.append((arg,))
For the second part of your question, the problem is just that you're slicing from positional_index:however_many
instead of positional_index:positional_index+however_many
. Also, your dict/set confusion is because you're using a set comprehension instead of a dict comprehension. Plus, you don't need that call to tuple
. Do it like this:
for pair in format:
however_many = len(pair[0])
argues.append((args[positional_index:positional_index+however_many], {k: kwargs[k] for k in pair[1]}))
positional_index += however_many
Note, though, that one problem with this is you'll be unable to pass different-valued keyword arguments of the same name to different functions. If you have def foo(a, b, c=2)
and def bar(a, b, c=8)
, you have no way to pass {'c': 1}
to foo
and {'c': 2}
to bar
, because you're only passing one dict at the beginning, which can only have one 'c'
key.
More generally, if you really want to handle any legal argument set, it won't be as simple as this, because even "positional" arguments can be passed by keyword. If you have a function like def foo(a, b, c=3, d=4)
, it's legal to call it with foo(1, d=8, c=7, b=6)
--- you can pass the keyword arguments out of order, and also pass a value for b
by keyword, even though it has no default value. So you can't in general just grab positional arguments based on the number of arguments without default values; you have to actually look at which particular arguments were passed by keyword, and only positionally pass the ones that weren't passed by keyword. (You could of course just say that your function won't work for this sort of case; it depends how general you want it to be.)