2 Iterables
for
Loops and Comprehensions
for
loops in Python are used to iterate over any iterable (like lists, tuples, strings, sets, dictionaries, generators, etc.).
Syntax:
for item in iterable:
# do something with item
Common Use Cases & Idioms:
Basic Iteration
= ["Alice", "Bob", "Charlie"]
names for name in names:
print(name)
Iterating with Index (use enumerate
)
for i, name in enumerate(names):
print(f"{i}: {name}")
Iterating Multiple Sequences (use zip
)
= [25, 30, 22]
ages for name, age in zip(names, ages):
print(f"{name} is {age} years old")
Iterating Over Dictionaries
= {"name": "Alice", "age": 25}
person for key, value in person.items():
print(key, value)
Nested Loops
for i in range(3):
for j in range(2):
print(i, j)
What Are Comprehensions?
Comprehensions are concise expressions for generating new iterables (like lists, sets, or dicts) using the syntax of a for
loop inside a single line.
Types and Idiomatic Patterns
List Comprehension (most common)
= [x**2 for x in range(5)]
squares # Output: [0, 1, 4, 9, 16]
Conditional List Comprehension
= [x for x in range(10) if x % 2 == 0]
evens # Output: [0, 2, 4, 6, 8]
Set Comprehension
= {len(word) for word in ["a", "ab", "abc", "ab"]}
unique_lengths # Output: {1, 2, 3}
️ Dict Comprehension
= ["apple", "banana", "cherry"]
words = {word: len(word) for word in words}
lengths # Output: {'apple': 5, 'banana': 6, 'cherry': 6}
Nested Comprehensions (2D lists)
= [[i * j for j in range(3)] for i in range(3)]
matrix # Output: [[0, 0, 0], [0, 1, 2], [0, 2, 4]]
enumerate()
enumerate()
is a built-in Python function that adds a counter to any iterable (like a list, tuple, or string), returning an enumerate object, which yields (index, value)
pairs on iteration.
Type:
type(enumerate(['a', 'b', 'c'])) # <class 'enumerate'>
Like zip
, it’s a lazy iterable, meaning it produces values on demand and can be turned into a list or looped over.
Basic Example:
= ['apple', 'banana', 'cherry']
fruits
for i, fruit in enumerate(fruits):
print(i, fruit)
Output:
0 apple
1 banana
2 cherry
Common & Idiomatic Use Cases for enumerate()
- Avoid Manual Indexing with
range(len(...))
Instead of:
for i in range(len(fruits)):
print(i, fruits[i])
Do this:
for i, fruit in enumerate(fruits):
print(i, fruit)
Cleaner, more Pythonic.
- Start Index at a Custom Value
for i, fruit in enumerate(fruits, start=1):
print(f"{i}. {fruit}")
Output:
1. apple
2. banana
3. cherry
Great for user-friendly numbering (e.g. starting from 1 instead of 0).
- Tracking Position in File or Data
with open("file.txt") as f:
for lineno, line in enumerate(f, start=1):
print(f"Line {lineno}: {line.strip()}")
Common in data processing and log parsing.
- Enumerate with Conditional Logic
= ['red', 'blue', 'green', 'blue']
colors for i, color in enumerate(colors):
if color == 'blue':
print(f"'blue' found at index {i}")
Helps track positions that meet a condition.
- Use with
zip()
for Triple Iteration
= ['x', 'y', 'z']
a = [10, 20, 30]
b for i, (x, y) in enumerate(zip(a, b)):
print(f"{i}: {x}-{y}")
Combines enumeration with parallel iteration.
🔚 Summary:
Function | What it does | Output form |
---|---|---|
zip(a, b) |
Combines sequences | (a[i], b[i]) |
enumerate(x) |
Adds index to an iterable | (i, x[i]) |
enumerate(x, start=n) |
Like above, but starts at n |
(n, x[0]) , (n+1, x[1]) , … |
zip()
The built-in zip()
function takes two or more iterables (like lists, tuples, or strings) and aggregates elements from each iterable by position (i.e. index). It returns an iterator of tuples, where the i-th tuple contains the i-th element from each of the input iterables.
zip(iterable1, iterable2, ...)
It stops when the shortest input iterable is exhausted.
You can think of zip()
as:
zip(A, B, C) ⇒ [(A[0], B[0], C[0]), (A[1], B[1], C[1]), ...]
No matter the input shape, zip()
always does the same thing: Group elements by position across multiple iterables.
zip()
returns a zip object, which is an iterator. You need to explicitly convert it into a list or tuple to see the full output:
list(zip(...)) # common
tuple(zip(...)) # possible
Common Use Cases and Idiomatic Patterns
- Combining Lists (Zipping)
= ['a', 'b', 'c']
letters = [1, 2, 3]
numbers
= list(zip(letters, numbers))
zipped print(zipped)
# Output: [('a', 1), ('b', 2), ('c', 3)]
Useful for:
- Pairing related data.
- Iterating in parallel over multiple lists.
- Looping Over Zipped Values
= ['Alice', 'Bob']
names = [85, 92]
scores
for name, score in zip(names, scores):
print(f"{name} scored {score}")
# Output:
# Alice scored 85
# Bob scored 92
This is an idiomatic way to loop over multiple sequences in sync.
- Unzipping (Inverse of zip) with
*
Unpacking
= [('a', 1), ('b', 2), ('c', 3)]
pairs
= zip(*pairs)
letters, numbers
print(letters) # Output: ('a', 'b', 'c')
print(numbers) # Output: (1, 2, 3)
Explanation:
*pairs
unpacks the list into separate arguments:zip(('a', 1), ('b', 2), ...)
zip()
groups by position: first elements, second elements, etc.
This is effectively transposing a 2D structure.
- Creating Dictionaries
= ['name', 'age']
keys = ['Alice', 30]
values
= dict(zip(keys, values))
dictionary print(dictionary)
# Output: {'name': 'Alice', 'age': 30}
A common idiom when you have two separate sequences representing keys and values.
- Zipping with Unequal Lengths
= [1, 2, 3]
a = ['x', 'y']
b
print(list(zip(a, b)))
# Output: [(1, 'x'), (2, 'y')]
Only pairs up to the shortest iterable. (See itertools.zip_longest()
if you want padding.)
Unified Understanding: Zip vs. “Unzip”
Why zip()
seems to do two very different things:
- Zipping: Combine separate lists into paired tuples.
- Unzipping: Split paired tuples into separate lists.
clarification:
# Zipping
= ['a', 'b', 'c']
list1 = [1, 2, 3]
list2 = list(zip(list1, list2))
zipped # Output: [('a', 1), ('b', 2), ('c', 3)]
# Unzipping
= [('a', 1), ('b', 2), ('c', 3)]
pairs = list(zip(*pairs))
unzipped # Output: [('a', 'b', 'c'), (1, 2, 3)]
Even though the intent differs, the operation is identical:
Group elements by position across the given iterables.
- In zipping, the elements come from separate sequences.
- In unzipping, the unpacking
*
turns a list of tuples into separate positional iterables, and zip groups those.
So:
zip(A, B)
zips rows.zip(*rows)
transposes the matrix — an “unzip” operation in spirit, but still just zip applied to unpacked input.
Bonus: Visual Matrix Analogy
Consider this “table” of rows (a list of tuples):
= [('a', 1),
rows 'b', 2),
('c', 3)] (
If you do:
zip(*rows)
You’re transposing it into:
'a', 'b', 'c'), (1, 2, 3)] [(
This is column-wise grouping.
Summary
zip()
is a fundamental tool for working with multiple iterables in parallel.- Always groups by index.
- Use it to zip, loop, unzip, transpose, and build dictionaries.
- When used with
*
, you can reverse its effect by unpacking rows into inputs.
It’s simple, powerful, and highly idiomatic in Python.
map()
and filter()
What is map()
?
map(func, iterable)
applies the function func
to each item in the iterable, returning a map object (an iterator).
Basic Use:
= [1, 2, 3, 4]
nums = list(map(lambda x: x**2, nums))
squared # Output: [1, 4, 9, 16]
What is filter()
?
filter(func, iterable)
selects items from the iterable for which func(item)
is true, returning a filter object (an iterator).
= [1, 2, 3, 4]
nums = list(filter(lambda x: x % 2 == 0, nums))
evens # Output: [2, 4]
Idiomatic Use Cases:
Apply Transformation to All Elements
= list(map(str.upper, ["a", "b", "c"]))
uppercased # Output: ['A', 'B', 'C']
Filter with Condition
= list(filter(lambda w: len(w) < 4, ["a", "apple", "bat", "cat"]))
short_words # Output: ['a', 'bat', 'cat']
Combine with zip
= [1, 2, 3]
a = [4, 5, 6]
b = list(map(lambda x: x[0] + x[1], zip(a, b)))
summed # Output: [5, 7, 9]
Equivalent List Comprehensions (more Pythonic)
# Instead of map
**2 for x in nums]
[x
# Instead of filter
for x in nums if x % 2 == 0] [x
Note: While map
and filter
are perfectly valid, list comprehensions are often preferred in Python due to better readability.
Final Recap Table
Concept | Description | Common Use Cases |
---|---|---|
for loop |
Iterates over any iterable | Basic iteration, nested loops |
Comprehension | Concise iterable construction | List/set/dict creation, filtering |
map(func, it) |
Apply func to all items |
Transform elements |
filter(func, it) |
Keep items where func(item) is True |
Selective filtering |
Extended Unpacking in Python with *
and **
Python allows powerful unpacking syntax to distribute or collect values in assignments and function calls.
Sequence Unpacking (with *
)
Standard unpacking:
= [1, 2, 3]
a, b, c print(a, b, c)
Output:
1 2 3
Extended unpacking:
*b = [1, 2, 3, 4]
a, print(a, b)
Output:
1 [2, 3, 4]
*a, b = [1, 2, 3, 4]
print(a, b)
Output:
[1, 2, 3] 4
*b, c = [1, 2, 3, 4, 5]
a, print(a, b, c)
Output:
1 [2, 3, 4] 5
Unpacking in Function Calls (with *
and **
)
Positional unpacking with *
:
def add(a, b, c):
return a + b + c
= [1, 2, 3]
nums print(add(*nums))
Output:
6
Keyword unpacking with **
:
def greet(name, greeting):
return f"{greeting}, {name}!"
= {'name': 'Alice', 'greeting': 'Hello'}
data print(greet(**data))
Output:
Hello, Alice!
Function Definitions with *args
and **kwargs
def show_args(*args):
print(args)
1, 2, 3) show_args(
Output:
(1, 2, 3)
def show_kwargs(**kwargs):
print(kwargs)
=1, b=2) show_kwargs(a
Output:
{'a': 1, 'b': 2}
Mixing Both *args
and **kwargs
def demo(a, b, *args, **kwargs):
print(f"a = {a}")
print(f"b = {b}")
print(f"args = {args}")
print(f"kwargs = {kwargs}")
= [1, 2, 3, 4]
pos = {'x': 10, 'y': 20}
kw *pos, **kw) demo(
Output:
a = 1
b = 2
args = (3, 4)
kwargs = {'x': 10, 'y': 20}
Comparing Similar Function Calls
def mixed(a, *rest):
print(f"a = {a}")
print(f"rest = {rest}")
= [1, 2, 3]
l = 0
a *l) mixed(a,
Output:
a = 0
rest = (1, 2, 3)
mixed(a, l)
Output:
a = 0
rest = ([1, 2, 3],)
Summary Table
Context | Syntax | What it Does | Example |
---|---|---|---|
Assignment | *var |
collects excess items into a list | a, *b = [1,2,3] → b=[2,3] |
Function call | *seq |
unpacks iterable into positional arguments | f(*[1,2]) → f(1,2) |
Function call | **dict |
unpacks dictionary into keyword arguments | f(**{'x':1}) → f(x=1) |
Function definition | *args |
collects extra positional arguments as tuple | def f(*args) |
Function definition | **kwargs |
collects extra keyword arguments as dictionary | def f(**kwargs) |