f in x
List Comprehension, Generators, and Iterators in Python: A Practical Guide
> cd .. / HUB_EDITORIALE
Analisi dei dati e metriche

List Comprehension, Generators, and Iterators in Python: A Practical Guide

[2026-06-04] Author: Ing. Calogero Bono

If you write Python and still use plain for loops to build lists or process data, you are wasting resources. We see it in the projects that come to us: slower code, unnecessary memory usage, server loads that grow for no reason. Here at Meteora Web, we think in terms of efficiency and cost. A well-written list comprehension is not just more elegant – it consumes fewer CPU cycles and makes code maintainable. A generator, instead of loading everything into RAM, can reduce memory usage by 90% on real datasets. This guide gets straight to the point: understand how they work, when to use them, and when not to, with copy‑and‑paste examples.

List Comprehension: syntax, power, and limits

List comprehension is the Pythonic way to create lists in one line. The basic structure is [expression for element in iterable if condition]. The order mirrors nested loops: first the for, then optional if.

Practical example: filtering orders

Imagine you have a list of order totals and want only those above €100. Classic loop:

orders = [45, 120, 80, 200, 55]
filtered = []
for price in orders:
    if price > 100:
        filtered.append(price)
print(filtered)  # [120, 200]

With list comprehension:

filtered = [price for price in orders if price > 100]
print(filtered)  # [120, 200]

Fewer lines, same readability. But don't overdo it. If the condition is complex or you need three nested for loops, stick to a normal loop. Readability matters more than brevity.

Transforming every element

You can transform data on the fly. Example: applying 22% VAT to net prices:

net_prices = [10, 25, 50]
with_vat = [round(p * 1.22, 2) for p in net_prices]
print(with_vat)  # [12.2, 30.5, 61.0]

Nested list comprehensions

They work like nested loops. To flatten a matrix:

matrix = [[1,2,3], [4,5], [6]]
flattened = [num for row in matrix for num in row]
print(flattened)  # [1, 2, 3, 4, 5, 6]

Note the order: outer loop first, then inner. Read left to right as if they were nested.

Generators: when memory becomes a problem

A list comprehension builds the entire list in memory. For a million elements, that's several MB of RAM. A generator, however, produces one item at a time – lazy evaluation. The syntax is identical but with parentheses instead of brackets.

numbers = range(1_000_000)
# Generator: consumes no memory
gen = (x * 2 for x in numbers)
print(next(gen))  # 0
print(next(gen))  # 2

You cannot index a generator or know its length without exhausting it. But you can iterate over it with a for loop or convert it to a list (losing the advantage).

Yield: custom generators

Using yield you create generator functions. Example: reading a log file line by line without loading it all into RAM:

def read_log(path):
    with open(path, 'r') as f:
        for line in f:
            yield line.strip()

for entry in read_log('server.log'):
    if 'ERROR' in entry:
        print(entry)

Each call to next() resumes execution from where it paused. It's like a frozen loop. Useful for data pipelines or infinite streams.

Iterators: the engine under the hood

List comprehensions, generators, for loops – everything relies on iterators. An iterable is any object that returns an iterator via iter(). The iterator implements __next__() and raises StopIteration when exhausted.

numbers = [1, 2, 3]
it = iter(numbers)
print(next(it))  # 1
print(next(it))  # 2
print(next(it))  # 3
# print(next(it))  # StopIteration

You can create custom iterators by defining __iter__ and __next__. Example: a counter that starts at a value and stops at a limit:

class Counter:
    def __init__(self, start, stop):
        self.current = start
        self.stop = stop
    def __iter__(self):
        return self
    def __next__(self):
        if self.current > self.stop:
            raise StopIteration
        val = self.current
        self.current += 1
        return val

for num in Counter(1, 5):
    print(num)  # 1 2 3 4 5

Generators are a simpler way to write iterators, but understanding the mechanics helps you debug and optimize them.

Performance comparison: list comprehension vs generators

We always measure. Use timeit to test:

import timeit

# List comprehension: 1 million squares
list_time = timeit.timeit('[x2 for x in range(1_000_000)]', number=1)
print(f"List comprehension: {list_time:.4f} sec")  # ~0.06 sec

# Generator: same calculation, lazy
gen_time = timeit.timeit('(x2 for x in range(1_000_000))', number=1)
print(f"Generator: {gen_time:.4f} sec")  # ~0.000001 sec (no execution!)

# If we then iterate the generator
gen = (x**2 for x in range(1_000_000))
def consume():
    for _ in gen:
        pass
gen_iter_time = timeit.timeit('consume()', globals={'consume': consume}, number=1)
print(f"Generator iterated: {gen_iter_time:.4f} sec")  # ~0.08 sec

The takeaway: creating a generator is instant, but total iteration is similar to a list comprehension. The real win comes when you don't need to consume everything. For example, if you're searching for the first element that satisfies a condition, a generator stops early.

Common mistakes and how to avoid them

  • Using list comprehension when a generator is needed: if you're passing data to a function that accepts an iterable (e.g., sum(), any(), max()), there's no need for an intermediate list. Use a generator: sum(x**2 for x in range(10)).
  • Reusing a generator: once exhausted, it's empty. If you need to iterate twice, convert it to a list (or recreate it).
  • Overly complex list comprehensions: if you have more than two for loops or conditions, write a normal loop with comments. Code must be understandable by others (or by you in six months).
  • Ignoring variable scope: in list comprehensions, the loop variable leaks into the namespace in Python 2; in Python 3 it's local. Either way, avoid modifying external variables inside a comprehension.

A quick decision checklist

  1. Do I need to produce a list? → List comprehension.
  2. Do I need to process a stream one item at a time? → Generator.
  3. Do I only want to iterate once and stop early? → Generator.
  4. Is the logic complex? → Normal loop or generator function with yield.
  5. Is readability the top priority? → Write the clearest code, even if longer.

In a nutshell – what to do now

Right now. Review your Python code: find for loops that populate lists. Replace them with list comprehensions where the transformation is linear. Where possible, switch to generators to reduce memory. Benchmark with timeit. Learn the iterator protocol – it's the foundation of async libraries and web frameworks. At Meteora Web, we use these concepts every day in our Laravel and Python projects to handle data streams efficiently, even on shared servers. Saving resources means lower costs and happier clients.

If you want to dive deeper into iterators in real‑world scenarios, check out the official Python documentation on iterators.

Sponsored Protocol

Ing. Calogero Bono

> AUTHOR_EXTRACTED

Ing. Calogero Bono

Co-founder di Meteora Web. Ingegnere informatico, sviluppo ecosistemi digitali ad alte prestazioni. AI, automazione, SEO tecnica e infrastrutture web. Scrivo di tecnologia per rendere complesso… semplice.

[ Read Full Dossier ]

Hai bisogno di applicare questa strategia?

Esegui il protocollo di contatto per iniziare un progetto con noi.

> INIZIA_PROGETTO

Sponsored

> MW_JOURNAL

> READ_ALL()