JEPA4Japan · tutorials

Iteration and Comprehensions

1,021 words 5 min read #Python

Transform collections with enumerate, zip, sorted, and readable comprehensions.

Course progress Course outline 24 of 24 lessons available

One loop, one job

Chapter 7 gave you boxes called lists, dictionaries, and sets. Now Python can walk past every box and do one small job.

  1. Take one Python gets a value
  2. Do the job your loop body runs
  3. Move on repeat until done
A for loop visits one value at a time.

Ask for the value you need:

topics = ["Python", "Git", "SQL"]

for topic in topics:
    print("Practice", topic)

If you also need a display number, use enumerate():

for number, topic in enumerate(topics, start=1):
    print(number, topic)

Output:

1 Python
2 Git
3 SQL

start=1 changes the numbers made by enumerate(). It does not change the list or its real indexes. Use range(len(topics)) only when the numeric index itself is part of the job.

Match the two rows with zip()

Imagine two rows of cards. zip() clips the first cards together, then the second cards, and so on.

  1. Python 55 minutes
  2. Git 25 minutes
  3. SQL 40 minutes
zip() makes pairs from matching positions.
topics = ["Python", "Git", "SQL"]
minutes = [55, 25, 40]

print(list(zip(topics, minutes)))

Output:

[('Python', 55), ('Git', 25), ('SQL', 40)]

There is one important edge: ordinary zip() stops at the shortest input.

print(list(zip(["Python", "Git", "SQL"], [55, 25])))

Output:

[('Python', 55), ('Git', 25)]

SQL quietly disappears. If that would mean broken data, use zip(topics, minutes, strict=True). Python 3.10 or newer then raises ValueError when the lengths differ. Chapter 10 will teach you how to handle that error.

Sort without messing up the shelf

sorted() makes a new list. The original stays put.

minutes = [55, 25, 40]
ranked = sorted(minutes, reverse=True)

print(ranked)
print(minutes)

Output:

[55, 40, 25]
[55, 25, 40]

Use a small key function when Python should compare one part of each record:

def minutes_in(record):
    return record["minutes"]


records = [
    {"topic": "Python", "minutes": 40},
    {"topic": "Git", "minutes": 25},
    {"topic": "SQL", "minutes": 40},
]

print([record["topic"] for record in sorted(records, key=minutes_in, reverse=True)])

Output:

['Python', 'SQL', 'Git']

Python’s sort is stable: Python and SQL tie, so they keep their earlier order. Pass the function as key=minutes_in, without ().

Short warning: minutes.sort() changes that list and returns None. Use sorted(minutes) when you want a new result and the old shelf must stay unchanged.

Comprehensions are tiny collection machines

  1. Old collection 20, 35, 50
  2. Optional filter keep 30 or more
  3. New collection 35, 50
A comprehension builds a new collection in one clear pass.
minutes = [20, 35, 50]

long_sessions = [value for value in minutes if value >= 30]
topic_names = {name.strip().casefold() for name in [" Python ", "python", "Git"]}
lookup = {topic: value for topic, value in zip(["Python", "Git"], [20, 35])}

print(long_sessions)
print(sorted(topic_names))
print(lookup)

Output:

[35, 50]
['git', 'python']
{'Python': 20, 'Git': 35}

Square brackets make a list, braces with one expression make a set, and key: value makes a dictionary. A good comprehension has one obvious transformation and perhaps one simple filter. If you need several decisions, messages, or updates, open it into an ordinary loop. Easy to read beats short.

Build a study parade

Save this complete project as study_parade.py. It pairs the data strictly, sorts a new list, numbers the result, and checks that the source was not changed.

def make_records(topics, minutes):
    return [
        {"topic": topic, "minutes": duration}
        for topic, duration in zip(topics, minutes, strict=True)
    ]


def minutes_in(record):
    return record["minutes"]


def build_report(topics, minutes, target):
    ranked = sorted(make_records(topics, minutes), key=minutes_in, reverse=True)
    lines = []

    for number, record in enumerate(ranked, start=1):
        if record["minutes"] >= target:
            status = "target met"
        else:
            status = "keep going"
        lines.append(
            f"{number}. {record['topic']}: "
            f"{record['minutes']} minutes ({status})"
        )

    return lines


topics = ["Python", "Git", "SQL", "Testing"]
minutes = [55, 25, 40, 25]
topics_before = topics.copy()

report = build_report(topics, minutes, target=30)

assert topics == topics_before
assert report[0].startswith("1. Python")

print("Study parade")
for line in report:
    print(line)
print("Source still:", topics)

Run python3 study_parade.py. You should see:

Study parade
1. Python: 55 minutes (target met)
2. SQL: 40 minutes (target met)
3. Git: 25 minutes (keep going)
4. Testing: 25 minutes (keep going)
Source still: ['Python', 'Git', 'SQL', 'Testing']

Git stays before Testing because they tie and sorting is stable. If an AssertionError appears, one of the promised facts is no longer true.

Three tiny missions

  1. Number four snack names with enumerate(start=1).
  2. Try ordinary zip() with three names and two scores; then add strict=True and note the error type.
  3. Add a 60-minute session to the project, then use a comprehension to collect only topics that met the target.

Ready for Chapter 9?

  • I can loop over values directly.
  • I can add human-friendly numbers with enumerate().
  • I can explain why ordinary zip() may drop an item.
  • I can use sorted() without changing the source.
  • I can keep a comprehension simple and choose a loop when it grows crowded.
  • I ran the Study Parade and saw every assertion pass.

Next, you will put these jobs into small cooperating functions and learn where each function can see its names.