More handy tools

INPUT · Slides

A starting value for a key that is not there yet

01 / 07

The shape you used to write for grouping

Say you want fruit gathered by how many letters it has. Making an empty dictionary and running a for over it is the same as before.

This time, though, the values are lists. So when a key is not there yet, you have to put an empty list in first before you can append.

Those three lines of if follow you around every time.

fruits = ["apple", "plum", "melon", "pear", "grape"]groups = {}for f in fruits:    n = len(f)    if n not in groups:        groups[n] = []    groups[n].append(f)print(groups)

Result

{5: ['apple', 'melon', 'grape'], 4: ['plum', 'pear']}

02 / 07

With defaultdict the if goes away

defaultdict is a dictionary that quietly supplies a starting value the moment you touch a key that is not there yet.

You tell it what the starting value is when you make it. defaultdict(list) gives you an empty list.

So even with no groups[5] yet, you can go straight to append. All three lines of if have gone.

from collections import defaultdictfruits = ["apple", "plum", "melon", "pear", "grape"]groups = defaultdict(list)for f in fruits:    groups[len(f)].append(f)print(dict(groups))

Result

{5: ['apple', 'melon', 'grape'], 4: ['plum', 'pear']}

03 / 07

Printed as it is, it reads badly

print a defaultdict straight and the starting value comes out with it.

The contents are the same, but shown like that they are hard to read. So when you want to display one, convert it with dict(...).

Nothing inside changes, of course. You are only changing how it looks.

from collections import defaultdictgroups = defaultdict(list)groups["red"].append("apple")print(groups)print(dict(groups))

Result

defaultdict(<class 'list'>, {'red': ['apple']})
{'red': ['apple']}

04 / 07

Counting with defaultdict(int)

Pass int as the starting value and what you get is 0.

Which means you can write counts[w] += 1 without caring whether the key is there. What Counter did in the last lesson, you can now do by hand.

For plain counting, Counter is shorter. But defaultdict lets you choose what to add, so it bends further.

from collections import defaultdictwords = ["red", "blue", "red"]counts = defaultdict(int)for w in words:    counts[w] += 1print(dict(counts))

Result

{'red': 2, 'blue': 1}

05 / 07

Touching a key makes it exist

There is one thing to watch. Merely reading adds a key.

The moment you write counts["green"], "green" goes into the dictionary as 0. Look inside expecting a plain dictionary and you will be surprised by what has crept in.

When all you want is to ask whether something is there, use in. in adds nothing.

from collections import defaultdictcounts = defaultdict(int)print(counts["green"])print(dict(counts))print("blue" in counts)print(dict(counts))

Result

0
{'green': 0}
False
{'green': 0}

06 / 07

Choosing between this and Counter

They are close enough to be worth a rule of thumb.

  • counting, and nothing else — Counter. One line, and most_common comes with it
  • grouping, or adding up — defaultdict. Put things in a list, add scores together, whatever you decide

When you cannot choose, ask whether what you want can be said entirely with the word "count".

from collections import defaultdictrecords = [("Yui", 30), ("Haruto", 20), ("Yui", 50)]totals = defaultdict(int)for name, score in records:    totals[name] += scoreprint(dict(totals))

Result

{'Yui': 80, 'Haruto': 20}

07 / 07

What it means for boilerplate to disappear

Neither Counter nor defaultdict is a tool you cannot live without. This lesson opened by writing out the version without them, and that version was fine.

They are still worth learning, because what disappears is the line that was identical every time and never needed any thought. With it gone, the part that does need thought stands out.

The names, and a sense of when their moment comes. Take away those two and that is enough.