12

We know in Python 3.6 dictionaries are insertion ordered as an implementation detail, and in 3.7 insertion ordering can be relied upon.

I expected this to also be the case for subclasses of dict such as collections.Counter and collections.defaultdict. But this appears to only hold true for the defaultdict case.

So my questions are:

  1. Is it true that ordering is maintained for defaultdict but not for Counter? And, if so, is there a straightforward explanation?
  2. Should ordering of these dict subclasses in the collections module be considered implementation details? Or, for example, can we rely on defaultdict being insertion ordered like dict in Python 3.7+?

Here are my rudimentary tests:

dict: ordered

words = ["oranges", "apples", "apples", "bananas", "kiwis", "kiwis", "apples"]

dict_counter = {}
for w in words:
    dict_counter[w] = dict_counter.get(w, 0)+1

print(dict_counter)

# {'oranges': 1, 'apples': 3, 'bananas': 1, 'kiwis': 2}

Counter: unordered

from collections import Counter, defaultdict

print(Counter(words))

# Counter({'apples': 3, 'kiwis': 2, 'oranges': 1, 'bananas': 1})

defaultdict: ordered

dict_dd = defaultdict(int)
for w in words:
    dict_dd[w] += 1

print(dict_dd)

# defaultdict(<class 'int'>, {'oranges': 1, 'apples': 3, 'bananas': 1, 'kiwis': 2})
jpp
  • 159,742
  • 34
  • 281
  • 339

1 Answers1

17

Counter and defaultdict are both ordered now, and you can rely on it. Counter just doesn't look ordered because its repr was designed before dict ordering was guaranteed, and Counter.__repr__ sorts entries by descending order of value.

def __repr__(self):
    if not self:
        return '%s()' % self.__class__.__name__
    try:
        items = ', '.join(map('%r: %r'.__mod__, self.most_common()))
        return '%s({%s})' % (self.__class__.__name__, items)
    except TypeError:
        # handle case where values are not orderable
        return '{0}({1!r})'.format(self.__class__.__name__, dict(self))
user2357112
  • 260,549
  • 28
  • 431
  • 505
  • 3
    This is excellent. Just to add, it's possible to test this trivially by `list(Counter(words))`, i.e. the insertion ordering will be returned. Thank you! – jpp Sep 04 '18 at 21:45
  • 1
    Is the guarantee of order for defaultdict documented somewhere? – Forethinker May 10 '22 at 20:22