Python Comprehensions, Iterables, and Built-in Functions
Comprehensions provide a concise, declarative syntax for filtering, transforming, and constructing sequences in Python. Because their loops run at C-level speed inside the CPython bytecode interpreter, they are significantly faster than appending elements in explicit manual for loops.
1. Comprehensions: List, Dict, and Set
The Mental Model: Matching Comprehensions to for Loops
A comprehension reads from left to right, matching the nesting order of standard for loops:
# 1. List Comprehension with filtering:
raw_metrics = [12, -4, 45, 0, 88, -12, 102]
# Syntax: [EXPRESSION for ITEM in ITERABLE if CONDITION]
positive_metrics = [m for m in raw_metrics if m > 0]
print("Filtered positives:", positive_metrics) # Output: [12, 45, 88, 102]
# Equivalent traditional for-loop:
# positive_metrics = []
# for m in raw_metrics:
# if m > 0:
# positive_metrics.append(m)
# 2. Nested Comprehensions (Flattening matrices):
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
# Notice the nesting order reads left-to-right exactly like nested loops:
# for row in matrix: for item in row:
flattened = [val for row in matrix for val in row]
print("Flattened list:", flattened) # Output: [1, 2, 3, 4, 5, 6, 7, 8, 9]
# 3. Dict Comprehension (Inverting mappings or transforming keys):
hosts = ["db-01", "api-01", "worker-01"]
host_ports = {h: 8080 if "api" in h else 5432 for h in hosts}
print("Host to port:", host_ports)
# Output: {'db-01': 5432, 'api-01': 8080, 'worker-01': 5432}
# 4. Set Comprehension (Deduplicating during mapping):
raw_emails = ["ALICE@Example.com", "bob@test.com", " alice@example.com "]
clean_emails = {e.strip().lower() for e in raw_emails}
print("Unique clean emails:", clean_emails) # Output: {'alice@example.com', 'bob@test.com'}2. Generator Expressions vs. List Comprehensions
A list comprehension immediately allocates memory and populates every single element in RAM. A generator expression uses parentheses (...) and computes each value lazily on demand, maintaining an constant memory footprint:
import sys
# List Comprehension: Immediately creates 1,000,000 integers in RAM
list_data = [x * 2 for x in range(1_000_000)]
print("List RAM usage:", sys.getsizeof(list_data), "bytes") # Output: ~8.4 MB
# Generator Expression: Evaluates items on demand, one by one
gen_data = (x * 2 for x in range(1_000_000))
print("Generator RAM usage:", sys.getsizeof(gen_data), "bytes") # Output: ~200 bytes!
# Lazy evaluation trace:
def compute_item(n):
print(f"Computing value for {n}...")
return n * 10
lazy_stream = (compute_item(i) for i in range(3))
print("Generator constructed! No items computed yet.")
# Items are only computed as we step through them:
print("First item:", next(lazy_stream)) # Output: Computing value for 0... -> First item: 0
print("Second item:", next(lazy_stream)) # Output: Computing value for 1... -> Second item: 10TIP
When passing data directly into reduction functions like
sum(),min(),max(), orany(), omit the double parentheses to stream lazily:total = sum(x for x in records)instead ofsum([x for x in records]).
3. Iteration Built-ins: enumerate and zip(strict=True)
servers = ["web-prod-1", "web-prod-2", "web-prod-3"]
ip_addrs = ["10.0.1.10", "10.0.1.11", "10.0.1.12"]
# 1. enumerate: Yields tuple of (index, item), avoiding manual 'i = 0' counters
for idx, server in enumerate(servers, start=1):
print(f"Server #{idx}: {server}")
# 2. zip with strict=True (Python 3.10+):
# By default, zip silently drops trailing elements if one sequence is shorter!
# 'strict=True' guarantees that length mismatches raise ValueError in production:
try:
extra_ports = [8080, 8081] # Shorter list (only 2 items)
for s, ip, p in zip(servers, ip_addrs, extra_ports, strict=True):
print(f"{s} -> {ip}:{p}")
except ValueError as err:
print("Caught zip length mismatch:", err)
# Output: Caught zip length mismatch: zip() argument 3 is shorter than arguments 1-24. any(), all(), and Short-Circuit Reductions
any() and all() short-circuit execution as soon as the final answer is mathematically certain:
# Short-circuiting verification:
# 'any()' stops immediately at the first truthy element
checks = [False, False, True, False]
print("Is any check True?", any(checks)) # Output: True
# Practical threshold guard:
node_load_averages = [0.45, 0.88, 0.99, 0.22]
if any(load > 0.95 for load in node_load_averages):
print("CRITICAL ALERT: At least one cluster node is overloaded (>95%)!")
# 'all()' stops immediately at the first falsy element
replica_health = [True, True, False, True]
print("Are all replicas healthy?", all(replica_health)) # Output: False5. Multi-Key Sorting with sorted() and key Callables
Python’s sorting algorithm (Powersort / Timsort) is stable () and highly optimized. You can pass a key= callable that produces a comparison tuple:
pods = [
{"name": "nginx-ingress", "cpu_m": 450, "memory_mb": 512},
{"name": "auth-api", "cpu_m": 800, "memory_mb": 256},
{"name": "redis-cache", "cpu_m": 120, "memory_mb": 1024},
{"name": "billing-api", "cpu_m": 450, "memory_mb": 256},
]
# Multi-key sort:
# Primary sort: memory_mb descending (using negative sign -p["memory_mb"])
# Secondary sort: cpu_m ascending
sorted_pods = sorted(pods, key=lambda p: (-p["memory_mb"], p["cpu_m"]))
for p in sorted_pods:
print(f"Pod: {p['name']:15} | Mem: {p['memory_mb']:4}MB | CPU: {p['cpu_m']}m")Next Steps
- 05: Exceptions and Error Handling — Exception hierarchies, EAFP, and exception groups.
- 11: Iterators, Generators, and Itertools — Custom iterators and infinite streams.