Chapter 08 of 24 | Python

Collections: arrays, lists, and slices

Collections store groups of objects. Choose a collection based on ordering, mutability, uniqueness, lookup needs, and how the data will be used.

Lists and slicing

scores = [82, 95, 71]
scores.append(88)
first_two = scores[:2]
scores[0] = 84
print(first_two, scores)

A list is ordered and mutable. Indexes start at zero; negative indexes count from the end. Slices use a half-open range, so items[start:stop] includes the start but excludes the stop. A slice creates a new outer list, but nested mutable values remain shared.

Tuples, dictionaries, and sets

point = (3, 4)  # immutable sequence
person = {"name": "Ada", "active": True}
person["city"] = "London"
unique_tags = {"python", "code", "python"}
print(unique_tags)  # duplicates collapse

A tuple is useful for a fixed group of values, especially when unpacking makes the roles clear. A dictionary maps hashable keys to values; use get when a key may be absent or test membership with in. A set stores unique hashable elements and is useful for membership and set operations.

name = person.get("name", "Unknown")
if "email" not in person:
    person["email"] = "not provided"
common = {"python", "typing"} & {"typing", "testing"}

Comprehensions and unpacking

names = ["ada", "grace", "linus"]
upper_names = [name.upper() for name in names]
length_by_name = {name: len(name) for name in names}
first, *middle, last = [10, 20, 30, 40]

Comprehensions make simple transformations and filters concise. Avoid deeply nested comprehensions when a loop would better explain the steps. Unpacking assigns collection elements to several names and can make tuple-returning functions readable.

Aliasing and complexity

Assigning one list to another name does not copy it. Use list.copy() for a shallow copy or copy.deepcopy when nested independence is required. List append is amortized efficient, while searching for an item is linear. Dictionary and set membership is average constant time, though worst-case and security behavior depend on hashing and input.

Python dictionaries preserve insertion order as a language guarantee in modern Python. Do not confuse insertion order with sorted order; sort keys explicitly when a canonical ordering is required.

Beginner-to-master progression

Exercises

  1. Count words in a sentence using a dictionary, then return the most frequent word.
  2. Remove duplicates while preserving first-seen order.
  3. Compare list membership with set membership for a large search workload.
  4. Build a nested list, make a shallow copy, and explain which objects remain shared.

Iteration, mutation, and views

Iterating over a collection while changing its size can skip elements or produce confusing behavior. Prefer building a new result with a comprehension, or iterate over a copy when removal is required. Dictionaries and sets also have rules about changing their size during iteration; Python raises RuntimeError in common cases.

numbers = [1, 2, 3, 4, 5]
odds = [number for number in numbers if number % 2]

settings = {"theme": "dark", "language": "en"}
for key, value in list(settings.items()):
    if value is None:
        del settings[key]

Dictionary views such as keys() and items() are dynamic views over the dictionary rather than independent snapshots. They reflect later updates. Converting a view to a list creates a snapshot of the current entries, though the values themselves may still be mutable objects.

Hashing contracts and collision behavior

Sets and dictionaries use hashes to locate candidate entries, then equality to confirm a match. Hash collisions are allowed; the implementation must still distinguish unequal keys. For custom objects, equal objects must have equal hashes, and the hash must not change while an object is stored as a key. Defining equality on a mutable class can make it unhashable by default, which prevents a common source of broken lookup behavior.

Average lookup complexity is not a security guarantee for every possible key distribution. Python protects string hashing against some collision attacks, but applications should still bound input sizes and avoid assuming hostile data has harmless processing cost.

Choose data structures by workload

A list is suitable for ordered sequences and indexed access. A deque is better for frequent appends and removals at both ends. A dictionary maps keys to values; a set tracks membership. A tuple can group values that should not be structurally changed. A heap or specialized library structure can serve priorities or numeric workloads. These structures trade lookup, insertion, ordering, and memory overhead against one another.

Mastery checkpoint

  1. Write a function that accepts only the collection interface it requires, and show it works with both a list and a generator where appropriate.
  2. Implement a custom immutable key type with consistent equality and hashing.
  3. Benchmark membership across list and set for multiple sizes, while including set-construction cost when that matches the real workload.
  4. Identify whether a dictionary view or a list snapshot is required for a mutation task.
0% read