Python · lesson 10 of 34
Beginner
16- 1 Introduction to Python
- 2 Setting Up Python
- 3 Python Variables and Type System
- 4 Python Data Types
- 5 Python Operators
- 6 Python Strings
- 7 Control Flow in Python
- 8 Python Functions and Parameters
- 9 Python Data Structures
- 10 Python Collections
- 11 Python Modules and Packages
- 12 Object-Oriented Programming in Python
- 13 Encapsulation and Name Mangling in Python
- 14 Polymorphism and Duck Typing in Python
- 15 Abstraction with Abstract Base Classes in Python
- 21 Building a Python CLI Tool
Intermediate
12- 16 Multiple Inheritance and MRO in Python
- 17 Composition vs Inheritance in Python
- 18 Python Dataclasses
- 19 Error Handling in Python
- 20 File I/O, pathlib, and JSON in Python
- 22 Python Decorators and Closures
- 23 Python Generators
- 24 Python Context Managers
- 25 Python Concurrency
- 27 Testing Python Code
- 28 Python Security
- 34 Python Interview Prep
Advanced
6Python Collections
Work through list, dict, set and tuple, then deque, defaultdict, Counter and namedtuple from the collections module, each with its real printed output.
By SkillByExample · Updated
Python ships four built-in collections — list, dict, set, tuple — and the collections module adds specialised ones for queues, counting and grouping. All output below is from Python 3.13.
list
Lists are ordered and mutable; most methods change the list in place and return None.
items = [3, 1, 4, 1, 5]
items.append(9)
items.insert(0, 2)
items.remove(1) # first 1 only
print(items)
print(items.pop(), items)
result = items.sort()
print(result, items)
print(sorted(items, reverse=True), items)
print(items[1:3], items[-1], items.count(1))
$ python lists.py
[2, 3, 4, 1, 5, 9]
9 [2, 3, 4, 1, 5]
None [1, 2, 3, 4, 5]
[5, 4, 3, 2, 1] [1, 2, 3, 4, 5]
[2, 3] 5 1
items.sort() returned None — it sorts in place, so result = items.sort() is a common bug. sorted() builds a new list and leaves the original alone. remove(1) deleted only the first 1, which is why count(1) is still 1.
dict
Dicts map keys to values and keep insertion order (guaranteed since 3.7).
user = {"name": "Alice", "age": 30}
print(user.get("email"), user.get("email", "N/A"))
try:
user["email"]
except KeyError as e:
print("KeyError:", e)
user["email"] = "alice@example.com"
user.update(age=31)
print(user)
defaults = {"timeout": 30, "retries": 3}
merged = defaults | {"timeout": 60} # right side wins
print(merged)
for key, value in merged.items():
print(f"{key}={value}")
print({n: n * n for n in range(4)})
$ python dicts.py
None N/A
KeyError: 'email'
{'name': 'Alice', 'age': 31, 'email': 'alice@example.com'}
{'timeout': 60, 'retries': 3}
timeout=60
retries=3
{0: 0, 1: 1, 2: 4, 3: 9}
user["email"] raises for a missing key; .get() returns None or your default. Updating age kept it in its original position — only new keys go to the end. In defaults | {...} the right-hand dict wins on conflicts.
set
Sets hold unique hashable values and test membership in O(1).
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a | b, a & b, a - b, a ^ b)
print(sorted(set([3, 1, 3, 2, 1])))
tags = {"python", "web"}
tags.add("api")
tags.discard("missing") # silently does nothing
print("api" in tags, sorted(tags))
try:
{[1, 2]}
except TypeError as e:
print("TypeError:", e)
$ python sets.py
{1, 2, 3, 4, 5, 6} {3, 4} {1, 2} {1, 2, 5, 6}
[1, 2, 3]
True ['api', 'python', 'web']
TypeError: unhashable type: 'list'
The four operators are union, intersection, difference and symmetric difference. The string set is printed through sorted() deliberately: string hashes are randomised per process, so print(tags) could show a different order every run. A list can’t go in a set because it is mutable, and so unhashable.
tuple
Tuples are immutable sequences — handy for records, unpacking, and dict keys.
point = (3.0, 4.0)
x, y = point
first, *rest = (1, 2, 3, 4, 5)
print(x, y, first, rest)
cache = {(0, 0): "origin"}
print(cache[(0, 0)])
print(type((42)), type((42,)))
try:
point[0] = 9.0
except TypeError as e:
print("TypeError:", e)
$ python tuples.py
3.0 4.0 1 [2, 3, 4, 5]
origin
<class 'int'> <class 'tuple'>
TypeError: 'tuple' object does not support item assignment
Starred unpacking collects the leftovers into a list, not a tuple. (42) is just the integer 42 in parentheses — the comma, not the parentheses, makes a tuple.
collections.deque
A double-ended queue with O(1) operations at both ends; maxlen makes it a ring buffer.
from collections import deque
recent = deque(maxlen=3)
for n in range(1, 6):
recent.append(n)
print(recent)
recent.appendleft(0)
print(recent)
print(recent.popleft(), recent)
graph = {"A": ["B", "C"], "B": ["D"], "C": ["D"], "D": []}
queue, order = deque(["A"]), []
while queue:
node = queue.popleft()
if node not in order:
order.append(node)
queue.extend(graph[node])
print("BFS:", order)
$ python deques.py
deque([3, 4, 5], maxlen=3)
deque([0, 3, 4], maxlen=3)
0 deque([3, 4], maxlen=3)
BFS: ['A', 'B', 'C', 'D']
Appending to a full deque evicts from the opposite end: append dropped 1 and 2, and appendleft(0) pushed out 5. popleft() is what makes the breadth-first search cheap — on a list, pop(0) would shift every element.
collections.defaultdict
A dict that calls a factory to create the value for a missing key.
from collections import defaultdict
words = ["apple", "ant", "banana", "avocado", "blueberry"]
by_letter = defaultdict(list)
for word in words:
by_letter[word[0]].append(word)
print(dict(by_letter))
freq = defaultdict(int)
for ch in "mississippi":
freq[ch] += 1
print(dict(freq))
print(by_letter["z"], "z" in by_letter)
$ python defaultdicts.py
{'a': ['apple', 'ant', 'avocado'], 'b': ['banana', 'blueberry']}
{'m': 1, 'i': 4, 's': 4, 'p': 2}
[] True
list and int are the factories: a missing key gets [] or 0, so append and += 1 work on first sight. The catch is the last line — merely reading by_letter["z"] inserted the key. Use .get() or in when you only want to look.
collections.Counter
A dict subclass for counting hashable things.
from collections import Counter
count = Counter(["apple", "banana", "apple", "cherry", "banana", "apple"])
print(count)
print(count.most_common(2))
print(count["grape"])
c1 = Counter(a=3, b=1)
c2 = Counter(a=1, b=2)
print(c1 + c2, c1 - c2)
print(Counter("hello world").most_common(3))
$ python counters.py
Counter({'apple': 3, 'banana': 2, 'cherry': 1})
[('apple', 3), ('banana', 2)]
0
Counter({'a': 4, 'b': 3}) Counter({'a': 2})
[('l', 3), ('o', 2), ('h', 1)]
A missing key reads as 0 without being inserted, unlike defaultdict. Subtraction drops counts that fall to zero or below, so b vanished from c1 - c2. Ties in most_common keep first-seen order: h beats e because it appeared first.
collections.namedtuple
A tuple whose positions also have names.
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3.0, 4.0)
print(p, p.x, p[1])
x, y = p
print(p._asdict())
print(p._replace(x=10.0), p)
try:
p.x = 5
except AttributeError as e:
print("AttributeError:", e)
$ python namedtuples.py
Point(x=3.0, y=4.0) 3.0 4.0
{'x': 3.0, 'y': 4.0}
Point(x=10.0, y=4.0) Point(x=3.0, y=4.0)
AttributeError: can't set attribute
_replace returns a new tuple; the original p is unchanged because namedtuples are immutable. For mutable records with type hints, use a dataclass instead.
Choosing the right collection
| Need | Use |
|---|---|
| Ordered sequence, mutable | list |
| Ordered sequence, immutable / hashable record | tuple |
| Key-value mapping | dict |
| Unique elements, fast membership | set |
| Fast queue (both ends), ring buffer | deque |
| Counting | Counter |
| Group-by / missing-key default | defaultdict |
| Lightweight named record | namedtuple or dataclass |
Practice
1. Given [5, 3, 5, 1, 3, 3], produce the unique values sorted in descending order using a set.
Solution
nums = [5, 3, 5, 1, 3, 3]
print(sorted(set(nums), reverse=True))
$ python unique_desc.py
[5, 3, 1]
set() removes duplicates; sorted(..., reverse=True) turns the unordered set back into an ordered list.
2. Sort the list of dicts [{"name": "Bob", "age": 25}, {"name": "Alice", "age": 30}, {"name": "Cy", "age": 25}] by age, then by name.
Solution
people = [{"name": "Bob", "age": 25}, {"name": "Alice", "age": 30}, {"name": "Cy", "age": 25}]
people.sort(key=lambda p: (p["age"], p["name"]))
for p in people:
print(p["age"], p["name"])
$ python sort_people.py
25 Bob
25 Cy
30 Alice
A tuple key sorts by its first element, then breaks ties with the next one.
3. Use Counter to find the two most common words in "the cat and the hat and the bat".
Solution
from collections import Counter
text = "the cat and the hat and the bat"
print(Counter(text.split()).most_common(2))
$ python top_words.py
[('the', 3), ('and', 2)]
4. Use a deque(maxlen=3) to print the moving average of the last three readings for [10, 20, 30, 40, 50].
Solution
from collections import deque
window = deque(maxlen=3)
for reading in [10, 20, 30, 40, 50]:
window.append(reading)
print(reading, sum(window) / len(window))
$ python moving_avg.py
10 10.0
20 15.0
30 20.0
40 30.0
50 40.0
Until the deque is full the average covers fewer values; after that the oldest reading falls off automatically.
5. Group ["apple", "bob", "cat", "dove", "egg"] by word length with a defaultdict, then show that checking 5 in groups does not create a key.
Solution
from collections import defaultdict
groups = defaultdict(list)
for w in ["apple", "bob", "cat", "dove", "egg"]:
groups[len(w)].append(w)
print(dict(groups))
print(5 in groups, 7 in groups, sorted(groups))
$ python by_length.py
{5: ['apple'], 3: ['bob', 'cat', 'egg'], 4: ['dove']}
True False [3, 4, 5]
in never calls the factory — only groups[key] on a missing key does.