30

Similarly to this question I'd like to create a collections.defaultdict with a default value of zero for counting elements of a list of tuples:

tuples = [('foo', 5), ('foo', 5), ('bar', -5), ('bar', -2)]
totals = defaultdict(0)
for t in tuples:
    totals[t[0]] += t[1]

But defaultdict(0) says:

TypeError: first argument must be callable

How am I supposed to create a "callable" zero?

Community
  • 1
  • 1
LondonRob
  • 73,083
  • 37
  • 144
  • 201
  • Also, note that as well as being covered by numerous other questions here, the `defaultdict(int)` example is *given in [the documentation](https://docs.python.org/2/library/collections.html#defaultdict-examples)*, so I'm really not sure this is adding value. – jonrsharpe Aug 05 '15 at 17:19
  • @jonrsharpe It being mentioned in the docs is a more powerful argument for closing than the duplicate question you linked to which, although a superset of this, doesn't quite have the Googleability. I have a tendency to make these Q&A style questions whenever the docstring is opaque and a quick Google doesn't instantly bring joy! – LondonRob Aug 05 '15 at 17:23

1 Answers1

54

The docstring for collections.defaultdict is pretty confusing at first read:

defaultdict(default_factory[, ...]) --> dict with default factory

The default factory is called without arguments to produce a new value when a key is not present, in getitem only. A defaultdict compares equal to a dict with the same items. All remaining arguments are treated the same as if they were passed to the dict constructor, including keyword arguments.

All this is trying to say is that the default "value" has to be a function, and that that function will be called without arguments whenever it's needed (i.e. whenever a normal dict would raise a KeyError.) The docstring refers to this function as default_factory.

The bit with [, ...] says that you can optionally pass in an object (or keywords) to use as the defaultdict's initial dictionary. Thus:

In [26]: x = defaultdict(lambda: 0, a=1, b=2)

In [28]: dict(x)
Out[28]: {'a': 1, 'b': 2}

In [29]: x['c'] # This would normally raise a `KeyError`
Out[29]: 0

In [30]: dict(x) # But here it just adds a new key!
Out[30]: {'a': 1, 'b': 2, 'c': 0}

Any function which returns zero when called with no arguments can be used:

In [31]: int()
Out[31]: 0

Thus, as per the counting example from the docs you could create a defaultdict with a default value of zero with:

defaultdict(int)
LondonRob
  • 73,083
  • 37
  • 144
  • 201