pythonpython-3.xdictionaryordereddictionary

Difference between dictionary and OrderedDict


I am trying to get a sorted dictionary. But the order of the items between mydict and orddict doesn't seem to change.

from collections import OrderedDict

mydict = {'a': 1, 'b': 2, 'c': 3, 'd': 4}

orddict = OrderedDict(mydict)

print(mydict, orddict)

# print items in mydict:
print('mydict')
for k, v in mydict.items():
    print(k, v)

print('ordereddict')
# print items in ordered dictionary
for k, v in orddict.items():
    print(k, v)


# print the dictionary keys
# for key in mydict.keys():
#     print(key)


#  print the dictionary values
# for value in mydict.values():
#     print(value)

Solution

  • As of Python 3.7, a new improvement to the dict built-in is:

    the insertion-order preservation nature of dict objects has been declared to be an official part of the Python language spec.

    This means there is no real need for OrderedDict anymore 🎉. They are almost the same.


    Some minor details to consider...

    Here are some comparisons between Python 3.7+ dict and OrderedDict:

    from collections import OrderedDict
    
    d = {'b': 1, 'a': 2}
    od = OrderedDict([('b', 1), ('a', 2)])
    
    # they are equal with content and order
    assert d == od
    assert list(d.items()) == list(od.items())
    assert repr(dict(od)) == repr(d)
    

    Obviously, there is a difference between the string representation of the two object, with the dict object in more natural and compact form.

    str(d)  # {'b': 1, 'a': 2}
    str(od) # OrderedDict([('b', 1), ('a', 2)])
    

    As for different methods between the two, this question can be answered with set theory:

    d_set = set(dir(d))
    od_set = set(dir(od))
    od_set.difference(d_set)
    # {'__dict__', '__reversed__', 'move_to_end'}  for Python 3.7
    # {'__dict__', 'move_to_end'}  for Python 3.8+
    

    This means OrderedDict has at most two features that dict does not have built-in, but work-arounds are shown here:

    Workaround for __reversed__ / reversed()

    No workaround is really needed for Python 3.8+, which fixed this issue. OrderedDict can be "reversed", which simply reverses the keys (not the whole dictionary):

    reversed(od)        # <odict_iterator at 0x7fc03f119888>
    list(reversed(od))  # ['a', 'b']
    
    # with Python 3.7:
    reversed(d)                    # TypeError: 'dict' object is not reversible
    list(reversed(list(d.keys()))) # ['a', 'b']
    
    # with Python 3.8+:
    reversed(d)        # <dict_reversekeyiterator at 0x16caf9d2a90>
    list(reversed(d))  # ['a', 'b']
    

    To properly reverse a whole dictionary using Python 3.7+:

    dict(reversed(list(d.items())))  # {'a': 2, 'b': 1}
    

    Workaround for move_to_end

    OrderedDict has a move_to_end method, which is simple to implement:

    od.move_to_end('b')  # now it is: OrderedDict([('a', 2), ('b', 1)])
    
    d['b'] = d.pop('b')  # now it is: {'a': 2, 'b': 1}
    

    Workaround for popitem

    OrderedDict and dict both have a similar popitem method that work the same for popping the last item:

    p1 = od.popitem(last=True)  # now it is: OrderedDict([('b', 1)])
    
    p2 = d.popitem()            # now it is: {'b': 1}
    
    p1 == p2 # same values: {'a': 2}
    

    How to pop the first item, since the popitem for dict doesn't take arguments:

    p1 = od.popitem(last=False)  # now it is: OrderedDict([('a', 2)])
    
    p2 = d.pop(next(iter(d)))    # now it is: {'a': 2}
    
    p1 == p2 # same values: {'b': 1}
    

    These approaches avoid iterating through the whole list to remove items, making them as fast in both cases as popitem in OrderedDict.