[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