[Python-ideas] Preserving **kwargs order
Andrew Barnert
abarnert at yahoo.com
Thu Mar 20 17:28:34 CET 2014
On Mar 20, 2014, at 5:58, MRAB <python at mrabarnett.plus.com> wrote:
> > No, a **kwargs parameter packs the keyword arguments into a dict, and
> > a **kwargs argument unpacks a dict into the keyword arguments;
> > that's exactly what they do. And dicts have arbitrary order.
> >
> When I decompile, what I get is:
>
> >>> dis(compile('''wrappee(**kwargs)''', '<string>', 'exec'))
> 1 0 LOAD_NAME 0 (wrappee)
> 3 LOAD_NAME 1 (kwargs)
> 6 CALL_FUNCTION_KW 0 (0 positional, 0 keyword pair)
> 9 POP_TOP
> 10 LOAD_CONST 0 (None)
> 13 RETURN_VALUE
This is basically an optimization, where the unpacking happens inside CALL_FUNCTION_KW instead of in a bunch of separate bytecodes that pushed them onto the stack to be popped off by CALL_FUNCTION. The end result is identical. Inside CALL_FUNCTION_KW, instead of starting with an empty dict and then popping keywords into it, it starts with a copy of kwargs and then pops keywords into it.
If that weren't true, you wouldn't be able to do this:
def foo(a, **kwargs): pass
foo(**{'a': 1, 'b': 2})
Or:
def foo(**kwargs): pass
foo(b=2, **{'a': 1})
(If you replace a or b with self, this should look more familiar.)
And, even if you don't have any missing or extra arguments, you are still going to get a new dict inside the callee. Otherwise this would do the wrong thing:
def foo(**kwargs): kwargs['a'] = 3
d = {'a': 1, 'b': 2}
foo(**d)
And, even if you changed Python so that, in the case where there are no extra or missing keywords and the dict isn't mutated inside foo, you just passed it as-is and skipped the copy, that _still_ wouldn't solve the forwarding problem, because it's still just a dict in arbitrary order. And adding a separate hidden parameter to pass the order along still wouldn't help unless you also forwarded _that_ parameter somehow.
And as far as I can see, there is no way to solve this problem. The only way to add keyword order information without breaking all existing forwarding functions is to make the **kwargs parameter always preserve order (and make the **kwargs calling syntax preserve order if present)--that is, to make it always an OrderedDict, which Guido has already rejected.
More information about the Python-ideas
mailing list