Python Lists and Dictionaries in Practice

AI-assistedDrafted with AI assistance and reviewed before publishing; examples are version-pinned. Verify against official docs when in doubt.

Python 3.13

Lists and dictionaries carry most of the data in a Python program. This chapter sticks to the operations that appear again and again, plus the handful of traps that silently produce wrong results.

Lists: add, remove, inspect

nums = [10, 20, 30]

nums.append(40)          # at the end
nums.insert(0, 5)        # at a position
nums.remove(20)          # by value, first match only
last = nums.pop()        # remove and return the last item
print(nums, last)        # [5, 10, 30] 40

del nums[0] removes by index. remove() raises ValueError when the value is absent, so check if x in nums first when absence is plausible.

append is amortized O(1); insert(0, x) and pop(0) are O(n) because every later element shifts. For queue-like work, use collections.deque.

Slicing

The syntax is seq[start:stop:step], with the end excluded:

nums = [0, 1, 2, 3, 4, 5]
nums[1:4]    # [1, 2, 3]
nums[:3]     # [0, 1, 2]
nums[::2]    # [0, 2, 4]
nums[::-1]   # [5, 4, 3, 2, 1, 0]

A slice creates a new list, which makes copy = nums[:] the shortest way to copy.

That copy is shallow. Nested lists are still shared:

matrix = [[1, 2], [3, 4]]
shallow = matrix[:]
shallow[0].append(9)
print(matrix)   # [[1, 2, 9], [3, 4]]

If the inner objects must be independent, use copy.deepcopy(matrix).

Iterating correctly

names = ["ann", "bob", "cindy"]

for i, name in enumerate(names):
    print(i, name)

for name, score in zip(names, [90, 85, 88]):
    print(name, score)

Do not add or remove items while iterating:

nums = [1, 2, 3, 4]
for n in nums:
    if n % 2 == 0:
        nums.remove(n)     # index shifts; [1, 3] here, wrong for [1, 2, 2, 3]

Build a new list instead:

nums = [n for n in nums if n % 2 != 0]

If you truly need to mutate in place, iterate over a copy: for n in nums[:].

Dictionaries: reading and iterating

user = {"name": "ann", "age": 30}

user["city"] = "Shenzhen"     # insert or update
user.pop("age")               # remove and return

Two ways to read a key, with different failure behavior:

user.get("nickname")            # None when missing
user.get("nickname", "anonymous")  # explicit default
user["nickname"]                # KeyError when missing

Use get() when the key may be absent; use [] when its absence is a bug you want to surface early.

Three iteration styles:

for key in user:                    # keys
    print(key)

for key, value in user.items():     # both
    print(key, value)

for value in user.values():         # values only
    print(value)

Since Python 3.7 dictionaries preserve insertion order, so list(user) returns keys in the order they were added.

Checking membership is O(1) on the key, not the value:

"name" in user          # True, fast
"ann" in user.values()  # True, but O(n)

Grouping without boilerplate

words = ["apple", "ant", "banana", "berry", "cherry"]
groups = {}

for word in words:
    groups.setdefault(word[0], []).append(word)

print(groups)
# {'a': ['apple', 'ant'], 'b': ['banana', 'berry'], 'c': ['cherry']}

setdefault inserts the default only when the key is missing, then returns the list, so the append always works. The alternative is collections.defaultdict(list), which creates the default on access:

from collections import defaultdict

groups = defaultdict(list)
for word in words:
    groups[word[0]].append(word)

Counting has its own tool:

from collections import Counter

Counter(words).most_common(2)   # [('apple', 1), ('ant', 1)]

Common mistakes

1. Assuming a slice is a deep copy

matrix[:] copies only the outer list. Mutating matrix[0] is visible through both references.

2. Removing items during iteration

Skipped elements, not an exception. Filter into a new list.

3. Using [] where get() belongs

A KeyError from optional configuration crashes the program. Conversely, get() on a required field hides a real bug.

4. Relying on sort() returning a list

sorted_nums = nums.sort()   # None
sorted_nums = sorted(nums)  # new list

sort() mutates in place and returns None; sorted() returns a new list.

5. Comparing lists with is

is compares identity. == compares element by element.

6. Forgetting that dict keys must be hashable

Lists and dicts cannot be keys; tuples and frozenset can.

Summary

  • append / pop at the end are cheap; front operations are O(n).
  • Slicing copies one level only; use deepcopy for nested data.
  • Never mutate a list while iterating it; build a new one.
  • get() for optional keys, [] for required ones.
  • setdefault / defaultdict / Counter remove most manual grouping and counting.

Next: tuples, sets, and what immutability really guarantees.