Python Atlas
Foundations

Iteration

Loop over iterables, pair and transform data, build comprehensions, and sort by keys.

Iteration processes one item at a time. Python's for loop asks an iterable for its items, so code usually loops over values directly instead of manually managing indices.

Direct iteration

total = 0

for price in [2.5, 4.0, 1.5]:
    total += price

print(total)  # 8.0

Strings, lists, tuples, dictionaries, sets, ranges, files, and many library objects are iterable.

Use range for a sequence of integers:

for number in range(2, 10, 2):
    print(number)  # 2, 4, 6, 8

The stop value is excluded. range(5) produces 0 through 4.

enumerate: value plus position

Use enumerate when both index and value matter:

languages = ["Python", "Rust", "Go"]

for position, language in enumerate(languages, start=1):
    print(f"{position}. {language}")

Prefer this to range(len(languages)); it is direct and less error-prone.

zip: parallel iteration

zip combines iterables position by position:

names = ["Ada", "Grace", "Guido"]
scores = [98, 95, 91]

for name, score in zip(names, scores, strict=True):
    print(f"{name}: {score}")

By default, zip stops at the shortest iterable, which can silently hide mismatched data. Python 3.10+ supports strict=True, which raises ValueError when lengths differ. Use it when equal lengths are an invariant.

Create a dictionary from paired data:

score_by_name = dict(zip(names, scores, strict=True))

while loops

Use while when repetition depends on a condition rather than a known iterable:

attempts = 3

while attempts > 0:
    print(f"{attempts} attempts remaining")
    attempts -= 1

Ensure something in the loop can make the condition false.

Controlling a loop

numbers = [3, -1, 0, 7]

for number in numbers:
    if number < 0:
        continue
    if number == 0:
        break
    print(number)
  • continue starts the next iteration.
  • break exits the nearest loop.
  • pass does nothing; it is a syntactic placeholder, not “continue.”

A loop's else runs only if no break occurred:

target = 7

for number in [2, 4, 6]:
    if number == target:
        print("Found")
        break
else:
    print("Not found")

Use this sparingly; a helper function returning early may be clearer.

Comprehensions

Comprehensions create containers from iterables:

squares = [number ** 2 for number in range(6)]
even_squares = [number ** 2 for number in range(10) if number % 2 == 0]
lengths = {word: len(word) for word in ["loop", "python"]}
initials = {name[0] for name in ["Ada", "Alan", "Grace"]}

Use a comprehension for one readable expression and optional simple filter. Use a regular loop for several transformations, side effects, nested complexity, or error handling.

map and filter

map(function, iterable) lazily transforms items. filter(function, iterable) lazily retains items for which the function is truthy:

raw_numbers = ["2", "5", "8"]
numbers = list(map(int, raw_numbers))
evens = list(filter(lambda number: number % 2 == 0, numbers))

print(numbers)  # [2, 5, 8]
print(evens)    # [2, 8]

They return iterators, so wrap them in list only when a list is actually needed.

Choose based on clarity:

numbers = [int(value) for value in raw_numbers]
evens = [number for number in numbers if number % 2 == 0]

Comprehensions are often clearer for inline Python expressions. map is concise when applying an existing named function such as int or str.strip. filter can be useful in functional pipelines, but a comprehension shows both item and condition together.

Iterators are consumed

An iterator produces each item once:

doubled = map(lambda number: number * 2, [1, 2, 3])
print(list(doubled))  # [2, 4, 6]
print(list(doubled))  # []

Create it again or materialize it once if multiple passes are required.

Sorting by a key

people = [
    {"name": "Grace", "age": 37},
    {"name": "Ada", "age": 36},
    {"name": "Guido", "age": 67},
]

by_age = sorted(people, key=lambda person: person["age"])
by_name_desc = sorted(
    people,
    key=lambda person: person["name"].casefold(),
    reverse=True,
)

The key function runs once per item and returns the comparison key. For common operations, named functions from operator, such as itemgetter, can replace a lambda.

Sort by multiple criteria with a tuple key:

records = [("Ada", 2), ("Grace", 1), ("Ada", 1)]
print(sorted(records, key=lambda record: (record[0], record[1])))

Common mistakes

  • Iterating over indices when direct iteration or enumerate is clearer.
  • Forgetting that range excludes its stop.
  • Allowing zip to silently truncate when equal lengths are required.
  • Modifying a collection's size while iterating over it.
  • Creating an infinite while loop by never changing its condition.
  • Reusing an exhausted iterator.
  • Packing too much logic into a comprehension or lambda.

Decision guidance

  • Use for for iterable data and while for condition-controlled repetition.
  • Use enumerate for index plus value and zip(..., strict=True) for aligned datasets.
  • Use a comprehension for a small transform/filter that creates a container.
  • Use map when applying an existing function reads naturally; use filter only when it is clearer than a comprehension.
  • Use sorted to preserve input and .sort() to intentionally mutate a list.

Practice

  1. Number a list of tasks starting at one with enumerate.
  2. Pair products and prices with strict zip, then create a dictionary.
  3. Produce squares of odd numbers from 1 through 19 with a comprehension.
  4. Sort (name, score) tuples by descending score, then by case-insensitive name.

LEARNING RECORD

Finish this lesson

Mark it complete when you can explain the main decision without looking.

KNOWLEDGE CHECK

Iteration

1What does `zip(names, scores, strict=True)` add to parallel iteration?
2Why does converting the same `map` object to a list a second time produce an empty list?