collections library
Not the same as the Collections page
This page covers the collections module — extra container types imported with
from collections import .... For the built-in list, dict, tuple, and set types
themselves, see Collections.
The collections module adds specialized containers with added functionaility on top of the
built-in str list dict tuple and set.
| Class | Works with | Example | Use it for |
|---|---|---|---|
Counter |
list, str, tuple, dict, set, range |
|
Counting how many times each item appears |
defaultdict |
dict |
|
Grouping items under keys that aren't known ahead of time |
namedtuple |
tuple, list |
|
A tuple whose positions have names instead of a mental lookup table |
deque |
list, str, tuple, dict, set, range |
|
A queue that's fast to add to or remove from at either end |
OrderedDict |
dict |
|
A dict where order itself matters for == comparisons |
ChainMap |
dict |
|
Layering overrides on top of defaults without merging them |
UserDict |
dict |
|
Subclassing dict when overriding a method directly doesn't work |
UserList |
list |
|
Subclassing list when overriding a method directly doesn't work |
UserString |
str |
|
Subclassing str when overriding a method directly doesn't work |
Install
collections ships with Python's standard library — nothing to install.
Import
Each class is imported individually by name, rather than through a collections. prefix —
so the import line differs per class, shown under its own "Import" heading below.
Counter
Counter takes any iterable — a list, string, tuple, dict (its keys), set, or range — of
hashable items (strings, numbers, booleans, other tuples) and returns a dict-like object
mapping each distinct item to how many times it appears.
species = ["ball", "burmese", "ball", "boa", "ball", "burmese"]
counts = Counter(species)
print(counts) # Counter({'ball': 3, 'burmese': 2, 'boa': 1})
Import
from collections import Counter
Counter operations
Count
-
counts[item]looks up an item's count. Missing items return0instead of raisingKeyError, unlike indexing a plain dict.counts["ball"] # 3 counts["reticulated"] # 0 -
most_common(n)returns thenhighest-count items as(item, count)tuples, sorted from most to least frequent. Leave outnto get all of them.counts.most_common(2) # [('ball', 3), ('burmese', 2)] -
total()adds up every count into a single number — the same result assum(counts.values()).counts.total() # 6
Inspect
-
elements()does the reverse of counting — expands aCounterback out into an iterator that repeats each item by its count.list(counts.elements()) # ['ball', 'ball', 'ball', 'burmese', 'burmese', 'boa']
Update
-
update()adds more items to an existingCounter, incrementing counts instead of replacing them — the counting equivalent of a list's.extend(). Passing anotherCounter(or a plain dict ofitem: count) adds its counts in directly.counts.update(["ball", "boa"]) # Counter({'ball': 4, 'burmese': 2, 'boa': 2}) -
subtract()isupdate()'s counting-down counterpart — decrements in place instead of returning a newCounter, and allows counts to go negative.counts.subtract({"ball": 1, "cobra": 1}) # ball: 3, cobra: -1
Combine
-
+/-/&/|combine twoCounterobjects item by item, returning a new one: add counts, subtract counts (dropping anything that would go negative), take the minimum of each count, or the maximum.more_counts = Counter({"ball": 1, "boa": 3}) counts + more_counts # Counter({'boa': 3, 'ball': 3, 'burmese': 1}) counts - more_counts # Counter({'ball': 1}) — burmese/boa dropped, not negative
Practice with Counter
Each box below is fully editable — write your answer, then click Run.
1. Count items. Build a Counter from species, then print how many times "cobra" appears.
from collections import Counter
species = ["cobra", "viper", "cobra", "mamba", "cobra"]
# your code here
2. Most common. Print the single most common item, as a (item, count) tuple.
from collections import Counter
species = ["cobra", "viper", "cobra", "mamba", "cobra"]
counts = Counter(species)
# your code here
3. Update and total. Add ["viper", "viper"] into counts, then print the total number of items counted.
from collections import Counter
species = ["cobra", "viper", "cobra", "mamba", "cobra"]
counts = Counter(species)
# your code here
Show solutions
# 1. Count items
from collections import Counter
species = ["cobra", "viper", "cobra", "mamba", "cobra"]
counts = Counter(species)
print(counts["cobra"])
# 2. Most common
from collections import Counter
species = ["cobra", "viper", "cobra", "mamba", "cobra"]
counts = Counter(species)
print(counts.most_common(1))
# 3. Update and total
from collections import Counter
species = ["cobra", "viper", "cobra", "mamba", "cobra"]
counts = Counter(species)
counts.update(["viper", "viper"])
print(counts.total())
defaultdict
A plain dict raises KeyError when indexing a missing key. defaultdict instead takes
a default_factory — a type like list/int/set, or any other zero-argument callable
— and calls it to create a value the first time a new key is used.
by_venomous = defaultdict(list)
snakes = [
{"species": "ball", "venomous": False},
{"species": "cobra", "venomous": True},
{"species": "burmese"}, # no "venomous" key — data wasn't recorded
]
for snake in snakes:
venomous = snake.get("venomous", "unknown")
by_venomous[venomous].append(snake["species"])
print(by_venomous)
# defaultdict(<class 'list'>, {False: ['ball'], True: ['cobra'], 'unknown': ['burmese']})
Import
from collections import defaultdict
Reading vs. writing
-
snake.get("venomous", "unknown")only reads — it falls back to"unknown"for the burmese python's missing key instead of raisingKeyError, the waysnake["venomous"]would. It doesn't touchby_venomousat all.snake.get("venomous", "unknown") # "unknown" -
by_venomous[venomous]is a write: appending to a list that has to exist first. A plain dict's.get()can't help there —by_venomous.get(venomous, []).append(...)would append to a throwaway list that's never stored back inby_venomous, silently losing the item.defaultdictmakes that write safe: indexing a key it hasn't seen before both creates the empty list and stores it, so.append()actually sticks — noif venomous not in by_venomous: by_venomous[venomous] = []check needed first. -
defaultdict(lambda: "unknown")shows the factory doesn't have to be a container type — any zero-argument callable works, so alambdacovers a scalar default too.by_species = defaultdict(lambda: "unknown") by_species["cobra"] # "unknown" — created on first access, not a KeyError
Practice with defaultdict
Each box below is fully editable — write your answer, then click Run.
1. Group by key. Group snakes into a defaultdict(list) keyed by "family", then print the whole result.
from collections import defaultdict
snakes = [
{"species": "cobra", "family": "elapid"},
{"species": "viper", "family": "viperid"},
{"species": "mamba", "family": "elapid"},
]
# your code here
2. Count with a factory. Build a defaultdict(int) counting how many snakes are in each family — increment by_family[family] += 1 inside the loop.
from collections import defaultdict
snakes = [
{"species": "cobra", "family": "elapid"},
{"species": "viper", "family": "viperid"},
{"species": "mamba", "family": "elapid"},
]
# your code here
Show solutions
# 1. Group by key
from collections import defaultdict
snakes = [
{"species": "cobra", "family": "elapid"},
{"species": "viper", "family": "viperid"},
{"species": "mamba", "family": "elapid"},
]
by_family = defaultdict(list)
for snake in snakes:
by_family[snake["family"]].append(snake["species"])
print(by_family)
# 2. Count with a factory
from collections import defaultdict
snakes = [
{"species": "cobra", "family": "elapid"},
{"species": "viper", "family": "viperid"},
{"species": "mamba", "family": "elapid"},
]
by_family = defaultdict(int)
for snake in snakes:
by_family[snake["family"]] += 1
print(by_family)
namedtuple
Builds a tuple subclass whose fields can be accessed by name (snake.species) as well as
by position (snake[0]) — a lightweight alternative to a full class when all it needs to
hold is a fixed group of fields.
Snake = namedtuple("Snake", ["species", "length_ft"])
snake = Snake("ball", 5)
print(snake.species) # "ball"
print(snake.length_ft) # 5
print(snake[0]) # "ball" — still works by position too
Like a plain tuple, a namedtuple instance is immutable — there's no snake.length_ft = 6.
Import
from collections import namedtuple
namedtuple operations
Create
-
namedtuple(name, fields)—fieldscan be a list of strings, or one space/comma-separated string ("species length_ft").Snake = namedtuple("Snake", ["species", "length_ft", "venomous"]) -
defaults=gives fields a default value, applied to the rightmost fields first — the same rule as default arguments on a regular function.Snake = namedtuple("Snake", ["species", "length_ft", "venomous"], defaults=[False]) snake = Snake("ball", 5) # venomous defaults to False -
_make(iterable)is the classmethod equivalent ofSnake(*iterable)— builds an instance from an existing list or tuple of values, handy when the values are already sitting in a sequence (e.g. a CSV row).row = ["burmese", 12] Snake._make(row) # Snake(species='burmese', length_ft=12, venomous=False)
Convert
-
_asdict()converts an instance to a regular dict.snake._asdict() # {'species': 'ball', 'length_ft': 5, 'venomous': False} -
_replace()returns a new instance with some fields changed, since the original can't be mutated.snake._replace(length_ft=6) # Snake(species='ball', length_ft=6, venomous=False)
Inspect
-
_fieldslists the field names;_field_defaultsreports the defaults as a dict, the same informationdefaults=set, mapped back to field names.Snake._fields # ('species', 'length_ft', 'venomous') Snake._field_defaults # {'venomous': False}
Practice with namedtuple
Each box below is fully editable — write your answer, then click Run.
1. Create and access. Build a Snake namedtuple with fields species and length_ft, make one for "boa" at 8 feet, then print .species.
from collections import namedtuple
# your code here
2. Replace a field. Using the snake from above, create a new instance with length_ft changed to 9, then print it.
from collections import namedtuple
Snake = namedtuple("Snake", ["species", "length_ft"])
snake = Snake("boa", 8)
# your code here
3. Convert to a dict. Print snake as a regular dict.
from collections import namedtuple
Snake = namedtuple("Snake", ["species", "length_ft"])
snake = Snake("boa", 8)
# your code here
Show solutions
# 1. Create and access
from collections import namedtuple
Snake = namedtuple("Snake", ["species", "length_ft"])
snake = Snake("boa", 8)
print(snake.species)
# 2. Replace a field
from collections import namedtuple
Snake = namedtuple("Snake", ["species", "length_ft"])
snake = Snake("boa", 8)
updated = snake._replace(length_ft=9)
print(updated)
# 3. Convert to a dict
from collections import namedtuple
Snake = namedtuple("Snake", ["species", "length_ft"])
snake = Snake("boa", 8)
print(snake._asdict())
deque
Pronounced "deck" — short for "double-ended queue." A deque works like a list, but adding or removing items
from the front (appendleft(), popleft()) is fast, where doing the same on a plain list
requires shifting every other item over. It's built from any iterable — a list, string,
tuple, dict (its keys), set, or range — the same as calling list() on one.
queue = deque(["ball", "burmese", "boa"])
queue.append("cobra") # add to the right end
queue.appendleft("blood") # add to the left end
print(queue) # deque(['blood', 'ball', 'burmese', 'boa', 'cobra'])
Import
from collections import deque
deque operations
Add
-
append()/appendleft()add one item to the right or left end.queue.append("viper") queue.appendleft("krait") -
extend()/extendleft()add several items at once.extendleft()reverses the order it's given, since each item is pushed to the front one at a time.queue.extend(["cobra", "viper"]) # add several to the right queue.extendleft(["ball", "blood"]) # add several to the left, one at a time -
insert()adds one item at a specific index, exactly like a list'sinsert().queue.insert(1, "viper")
Remove
-
pop()/popleft()remove and return the item from the right or left end.queue.pop() # removes and returns the last item queue.popleft() # removes and returns the first item -
remove()/clear()work exactly like their list equivalents: delete the first matching value, or empty the deque out entirely.queue.remove("boa") queue.clear()
Inspect
-
count()/index()count occurrences of a value, or find its first position — same as on a list.queue.count("ball") queue.index("cobra") -
copy()makes an independent copy — same as on a list.backup = queue.copy()
Reorder
-
rotate(n)shifts every itemnplaces to the right (or left, with a negativen), wrapping the ones that fall off the end back around to the other side.queue.rotate(1) -
reverse()flips the order in place — same as on a list.queue.reverse() -
maxlen=, passed when creating the deque, caps it at a fixed size. If the starting iterable already has more thanmaxlenitems, only the lastmaxlenof them are kept — the earlier ones are cut from the front, as if they'd already scrolled off.recent = deque(["ball", "burmese", "boa", "cobra"], maxlen=3) print(recent) # deque(['burmese', 'boa', 'cobra'], maxlen=3) — "ball" was cut
Once full, adding to either end drops one item off the other end to make room, which
makes it a ready-made "last n seen" tracker. Which side gets dropped depends on which
side you add to:
```python-ref
recent = deque(["ball", "burmese", "boa"], maxlen=3)
recent.append("cobra") # adds to the right — drops from the left
print(recent) # deque(['burmese', 'boa', 'cobra'], maxlen=3) — "ball" dropped
recent.appendleft("krait") # adds to the left — drops from the right
print(recent) # deque(['krait', 'burmese', 'boa'], maxlen=3) — "cobra" dropped
```
Practice with deque
Each box below is fully editable — write your answer, then click Run.
1. Add to both ends. Build a deque from ["burmese", "boa"], append "cobra" to the right, and "ball" to the left. Print the result.
from collections import deque
# your code here
2. Pop from both ends. Using the deque above, pop one item from the right and one from the left, then print both.
from collections import deque
queue = deque(["ball", "burmese", "boa", "cobra"])
# your code here
3. Bounded queue. Build a deque with maxlen=2, append "ball", "burmese", and "boa" one at a time, then print the final result.
from collections import deque
# your code here
Show solutions
# 1. Add to both ends
from collections import deque
queue = deque(["burmese", "boa"])
queue.append("cobra")
queue.appendleft("ball")
print(queue)
# 2. Pop from both ends
from collections import deque
queue = deque(["ball", "burmese", "boa", "cobra"])
right = queue.pop()
left = queue.popleft()
print(right, left)
# 3. Bounded queue
from collections import deque
recent = deque(maxlen=2)
for species in ["ball", "burmese", "boa"]:
recent.append(species)
print(recent)
OrderedDict
Until Python 3.7 (released in 2018), a plain dict didn't guarantee it would remember insertion order — OrderedDict existed specifically to add that guarantee. Now it's mostly seen in legacy code written before 3.7, and in code that specifically needs its reordering functionality.
snake = OrderedDict([("species", "ball"), ("length_ft", 5), ("venomous", False)])
Import
from collections import OrderedDict
OrderedDict operations
Reorder
-
move_to_end(key, last=True)relocates an existing key to the back (or, withlast=False, to the front).snake.move_to_end("species") # OrderedDict([('length_ft', 5), ('venomous', False), ('species', 'ball')]) -
popitem(last=True)removes and returns the last (or first, withlast=False) key-value pair — a plain dict'spopitem()can only ever take the last one.snake.popitem(last=False) # ('species', 'ball')
Compare
-
==checks order as well as contents — two plain dicts with the same items in a different order are still equal, but twoOrderedDictobjects aren't.OrderedDict([("a", 1), ("b", 2)]) == OrderedDict([("b", 2), ("a", 1)]) # False
ChainMap
Searches several dicts as if they were one, without copying or merging their contents. Looking up a key checks each dict in order and returns the first match — useful for layering a set of overrides on top of a set of defaults, where the underlying dicts might still change later and should stay separate.
defaults = {"venomous": False, "docile": True}
overrides = {"venomous": True} # this particular snake is an exception
snake = ChainMap(overrides, defaults)
print(snake["venomous"]) # True — found in overrides, checked first
print(snake["docile"]) # True — not in overrides, falls back to defaults
Writing to a ChainMap (snake["docile"] = False) only ever changes the first dict in the
chain — the rest are left untouched, read-only from the ChainMap's point of view.
Import
from collections import ChainMap
ChainMap operations
Extend
-
new_child(m)returns a newChainMapwithm(an empty dict by default) added to the front — useful for pushing a fresh, temporary layer of overrides on top without touching the original.scoped = snake.new_child({"venomous": None}) scoped["venomous"] # None — the new front dict wins
Inspect
-
.mapsis the underlying list of dicts, in search order, so it can be inspected or edited directly.snake.maps # [{'venomous': True}, {'venomous': False, 'docile': True}] -
.parentsis the reverse ofnew_child(): a newChainMapwith the first dict dropped.scoped.parents["venomous"] # True — back to what snake itself would return
User* wrapper classes
UserDict, UserList, and UserString wrap a plain dict, list, or str for
subclassing1. Subclassing dict/list/str directly is possible, but several of their
built-in methods internally bypass any method you've overridden — so an override doesn't
reliably run. The User* classes store the real data on a .data attribute instead, which
sidesteps that problem, at the cost of being another import.
class CaseInsensitiveDict(UserDict):
def __setitem__(self, key, value):
super().__setitem__(key.lower(), value)
snake = CaseInsensitiveDict()
snake["SPECIES"] = "ball"
print(snake) # {'species': 'ball'} — key was lowercased on the way in
Import
from collections import UserDict, UserList, UserString
This is a niche tool — reach for it only when subclassing dict, list, or str directly
turns out not to work, not as a first choice for everyday container code.
-
Defining a new class that inherits from another class, reusing (and optionally overriding) its attributes and methods. ↩