Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using an OrderedDict in **kwargs

Is it possible to pass an OrderedDict instance to a function which uses the **kwargs syntax and retain the ordering?

What I'd like to do is :

def I_crave_order(**kwargs):     for k, v in kwargs.items():         print k, v  example = OrderedDict([('first', 1), ('second', 2), ('third', -1)])  I_crave_order(**example) >> first 1 >> second 2 >> third -1 

However the actual result is:

>> second 2 >> third -1 >> first 1 

ie, typical random dict ordering.

I have other uses where setting the order explicitly is good, so I want to keep **kwargs and not just pass the OrderedDict as a regular argument

like image 817
theodox Avatar asked Nov 05 '14 01:11

theodox


People also ask

When should I use OrderedDict?

Intent signaling: If you use OrderedDict over dict , then your code makes it clear that the order of items in the dictionary is important. You're clearly communicating that your code needs or relies on the order of items in the underlying dictionary.

Are Python Kwargs ordered?

Specification. Starting in version 3.6 Python will preserve the order of keyword arguments as passed to a function. To accomplish this the collected kwargs will now be an ordered mapping.

What is OrderedDict ()?

An OrderedDict is a dictionary subclass that remembers the order that keys were first inserted. The only difference between dict() and OrderedDict() is that: OrderedDict preserves the order in which the keys are inserted.

What is the difference between dict and OrderedDict?

The OrderedDict is a subclass of dict object in Python. The only difference between OrderedDict and dict is that, in OrderedDict, it maintains the orders of keys as inserted. In the dict, the ordering may or may not be happen. The OrderedDict is a standard library class, which is located in the collections module.


2 Answers

As of Python 3.6, the keyword argument order is preserved. Before 3.6, it is not possible since the OrderedDict gets turned into a dict.


The first thing to realize is that the value you pass in **example does not automatically become the value in **kwargs. Consider this case, where kwargs will only have part of example:

def f(a, **kwargs):     pass example = {'a': 1, 'b': 2} f(**example) 

Or this case, where it will have more values than those in example:

example = {'b': 2} f(a=1, c=3, **example) 

Or even no overlap at all:

example = {'a': 1} f(b=2, **example) 

So, what you're asking for doesn't really make sense.

Still, it might be nice if there were some way to specify that you want an ordered **kwargs, no matter where the keywords came from—explicit keyword args in the order they appear, followed by all of the items of **example in the order they appear in example (which could be arbitrary if example were a dict, but could also be meaningful if it were an OrderedDict).

Defining all the fiddly details, and keeping the performance acceptable, turns out to be harder than it sounds. See PEP 468, and the linked threads, for some discussion on the idea. It seems to have stalled this time around, but if someone picks it up and champions it (and writes a reference implementation for people to play with—which depends on an OrderedDict accessible from the C API, but that will hopefully be there in 3.5+), I suspect it would eventually get into the language.


By the way, note that if this were possible, it would almost certainly be used in the constructor for OrderedDict itself. But if you try that, all you're doing is freezing some arbitrary order as the permanent order:

>>> d = OrderedDict(a=1, b=2, c=3) OrderedDict([('a', 1), ('c', 3), ('b', 2)]) 

Meanwhile, what options do you have?

Well, the obvious option is just to pass example as a normal argument instead of unpacking it:

def f(example):     pass example = OrderedDict([('a', 1), ('b', 2)]) f(example) 

Or, of course, you can use *args and pass the items as tuples, but that's generally uglier.

There might be some other workarounds in the threads linked from the PEP, but really, none of them are going to be better than this. (Except… IIRC, Li Haoyi came up with a solution based on his MacroPy to pass order-retaining keyword arguments, but I don't remember the details. MacroPy solutions in general are awesome if you're willing to use MacroPy and write code that doesn't quite read like Python, but that isn't always appropriate…)

like image 95
abarnert Avatar answered Sep 27 '22 20:09

abarnert


This is now the default in python 3.6.

Python 3.6.0a4+ (default:d43f819caea7, Sep  8 2016, 13:05:34) >>> def func(**kw): print(kw.keys()) ... >>> func(a=1, b=2, c=3, d=4, e=5) dict_keys(['a', 'b', 'c', 'd', 'e'])   # expected order 

It's not possible to do it before as noted by the other answers.

like image 44
damio Avatar answered Sep 27 '22 20:09

damio