Reputation: 1077
Why does my python OrderedDict get initialized 'out of order'?
The solution here is less intriguing than the explanation. There's something here I just don't get, and perhaps an expanation would help others as well as me.
>>> from collections import OrderedDict
>>> spam = OrderedDict(s = (1, 2), p = (3, 4), a = (5, 6), m = (7, 8))
>>> spam
OrderedDict([('a', (5, 6)), ('p', (3, 4)), ('s', (1, 2)), ('m', (7, 8))])
>>> for key in spam.keys():
... print key
...
# this is 'ordered' but not the order I wanted....
a
p
s
m
# I was expecting (and wanting):
s
p
a
m
Upvotes: 31
Views: 41503
Reputation: 8547
As the other answers have mentioned, trying to pass a dict to OrderedDict or using keyword arguments doesn't preserve the order. Passing in tuples is kinda ugly, though, and this is Python. It should be beautiful.
You can abuse __getitem__
on a class in order to have dict-like syntax for creating OrderedDict "literals":
from collections import OrderedDict
class OD(object):
"""This class provides a nice way to create OrderedDict "literals"."""
def __getitem__(self, slices):
if not isinstance(slices, tuple):
slices = slices,
return OrderedDict((slice.start, slice.stop) for slice in slices)
# Create a single instance; we don't ever need to refer to the class.
OD = OD()
Now you can use dict-like syntax to create an OrderedDict:
spam = OD['s': (1, 2),
'p': (3, 4),
'a': (5, 6),
'm': (7, 8)]
assert(''.join(spam.keys()) == 'spam')
This works because inside the square brackets, Python creates slice literals, which happen to look like dict syntax if you squint a little.
The OD
class could benefit from error checking, but this demonstrates how it can work.
Upvotes: 4
Reputation: 3155
From the docs:
The OrderedDict constructor and
update()
method both accept keyword arguments, but their order is lost because Python’s function call semantics pass-in keyword arguments using a regular unordered dictionary.
So initialization loses the ordering, because it's basically calling a constructor with **kwargs
.
Edit: In terms of a solution (not just an explanation)—as pointed out in a comment by the OP, passing in a single list of tuples will work:
>>> from collections import OrderedDict
>>> spam = OrderedDict([('s',(1,2)),('p',(3,4)),('a',(5,6)),('m',(7,8))])
>>> for key in spam:
... print(key)
...
s
p
a
m
>>> for key in spam.keys():
... print(key)
...
s
p
a
m
This is because it's only getting a single argument, a list.
Upvotes: 38
Reputation: 4772
@Chris Krycho gave a good explanation of why things fail.
If you look at the repr() of an OrderedDict you get a hint at how to impart order from the beginning: You need to use a list of (key, value) pairs to preserve the order of the keys given by the list.
Here's one I did earlier:
>>> from collections import OrderedDict
>>> spamher = OrderedDict(s=6, p=5, a=4, m=3, h=2, e=1, r=0)
>>> spamher
OrderedDict([('h', 2), ('m', 3), ('r', 0), ('s', 6), ('p', 5), ('a', 4), ('e', 1)])
>>>
>>> list(spamher.keys())
['h', 'm', 'r', 's', 'p', 'a', 'e']
>>>
>>> spamher = OrderedDict([('s', 6), ('p', 5), ('a', 4), ('m', 3), ('h', 2), ('e', 1), ('r', 0)])
>>> list(spamher.keys())
['s', 'p', 'a', 'm', 'h', 'e', 'r']
>>>
(It just so happened that in Python v3.3.0 your original example of spam
kept the keys in their original order from the outset. I changed to spamher
to get arounf this).
Upvotes: 17