Pythonで複数のリストを同時に処理したいときに頼れるのがzipです。
名前と年齢のような対応する要素をペアにしながら安全に走査でき、コードの可読性と保守性が高まります。
本記事では基本から実践パターン、長さ不一致の対処、アンパッキングまでを段階的に解説します。
zip()の基本: 複数リストを同時ループ
zipの使い方とforループ
zipは複数のイテラブル(リスト、タプル、文字列など)を要素ごとに束ねます。
束ねた結果はタプルの並びとして扱えます。
対応関係のある2つのリストを同時に処理する基本例です。
# 2つのリストを同時にループする基本
names = ["Alice", "Bob", "Carol"]
ages = [24, 19, 31]
for name, age in zip(names, ages):
# zipは(name, age)のタプルを順番に返すので、同時にアンパックできる
print(f"{name} is {age} years old")
Alice is 24 years old
Bob is 19 years old
Carol is 31 years old
ここでインデックス操作を使って並行走査するよりも、zipのほうが安全かつ読みやすく、インデックスずれのバグも防げます。
2つ以上のイテラブルを束ねる
zipは3つ以上にも対応します。
要素数が揃っていれば素直に同時ループできます。
# 3つのイテラブルを束ねる
names = ["Alice", "Bob", "Carol"]
ages = [24, 19, 31]
cities = ["Tokyo", "Osaka", "Nagoya"]
for name, age, city in zip(names, ages, cities):
print(f"{name:>5} | {age:>2} | {city}")
Alice | 24 | Tokyo
Bob | 19 | Osaka
Carol | 31 | Nagoya
Python3のzipはイテレータ(遅延評価)
Python3ではzipはイテレータ(遅延評価)です。
必要になるまで要素を生成しないため省メモリです。
反復すると使い切られる点に注意します。
# zipはイテレータなので必要になるまで実体化されない
z = zip([1, 2, 3], ["a", "b", "c"])
print(z) # zipオブジェクト
print(list(z)) # ここで初めてリストへ実体化
print(list(z)) # 2度目は空 (使い切られている)
<zip object at 0x...>
[(1, 'a'), (2, 'b'), (3, 'c')]
[]
繰り返し使いたい場合は、元データを再度zipし直すか、最初にlistへ変換してから使います。
- 関連記事:ジェネレータの基本と使い方: 遅延評価で省メモリ処理
- 関連記事:イテレーションを効率化 (chain,islice,permutations)の使い方
- イテレータ/イテラブルの説明 — Python 3.13.7 ドキュメント
実践: Pythonのzip()でよく使うパターン
2つのリストを並行処理
スコアを合算するなど、対応要素の演算に適しています。
# 要素ごとの合算 (ベクトル加算のイメージ)
a = [10, 20, 30]
b = [1, 2, 3]
sums = []
for x, y in zip(a, b):
sums.append(x + y)
print(sums)
[11, 22, 33]
3つ以上のリストを同時ループ
複数属性を扱うときも読みやすく書けます。
# 3系列の同時処理でメッセージを組み立てる
ids = [1001, 1002, 1003]
names = ["Alice", "Bob", "Carol"]
scores = [88, 72, 91]
for sid, name, score in zip(ids, names, scores):
status = "Pass" if score >= 80 else "Fail"
print(f"[{sid}] {name}: {score} ({status})")
[1001] Alice: 88 (Pass)
[1002] Bob: 72 (Fail)
[1003] Carol: 91 (Pass)
zip+dictでペアをマッピング
dictと組み合わせると、2つの系列から辞書を素早く作れます。
# キーと値のリストから辞書を作る
keys = ["id", "name", "age"]
values = [1001, "Alice", 24]
record = dict(zip(keys, values))
print(record)
{'id': 1001, 'name': 'Alice', 'age': 24}
キーが重複している場合、後から現れた値で上書きされます。
意図しない上書きを避けるため、キーの唯一性を事前に確認しましょう。
enumerateとzipの違いと併用
enumerateはインデックスと要素を返します。
zipは複数系列を束ねます。
両者は役割が異なり、併用するとインデックス付きの並行走査ができます。
names = ["Alice", "Bob", "Carol"]
ages = [24, 19, 31]
# enumerate(zip(...))でインデックス付きの同時ループ
for i, (name, age) in enumerate(zip(names, ages), start=1):
print(f"{i}: {name} ({age})")
# 代替: 明示的にインデックスをzipする (読みやすさの比較用)
for i, name, age in zip(range(1, len(names)+1), names, ages):
print(f"{i}: {name} ({age})")
1: Alice (24)
2: Bob (19)
3: Carol (31)
1: Alice (24)
2: Bob (19)
3: Carol (31)
一般にはenumerate(zip(...))のほうが意図が伝わりやすいです。
内包表記とzipで一行処理
リストや辞書の内包表記と組み合わせると簡潔に書けます。
a = [1, 2, 3]
b = [10, 20, 30]
# 要素ごとの積を新しいリストで得る
products = [x * y for x, y in zip(a, b)]
print(products)
# 2つの系列から辞書を作る (dict内包表記)
keys = ["x", "y", "z"]
vals = [1, 2, 3]
d = {k: v * v for k, v in zip(keys, vals)}
print(d)
[10, 40, 90]
{'x': 1, 'y': 4, 'z': 9}
- 関連記事:辞書内包表記/セット内包表記に入門する
長さが異なる場合の挙動と対処
zipは短い方で切り捨て
zipは最短のイテラブルまでで止まります。
余った要素は黙って無視されます。
a = [1, 2, 3, 4] # 長い
b = [10, 20] # 短い
print(list(zip(a, b))) # 短い方の長さ(2)で切り捨て
[(1, 10), (2, 20)]
データ欠落が問題になる場面ではそのまま使わないでください。
itertools.zip_longestで穴埋め
長さを揃えたい場合はitertools.zip_longestが便利です。
足りない部分をfillvalueで埋められます。
from itertools import zip_longest
a = [1, 2, 3, 4]
b = [10, 20]
# 足りない箇所をNoneで埋める
print(list(zip_longest(a, b, fillvalue=None)))
# 0など任意の値で埋める
print(list(zip_longest(a, b, fillvalue=0)))
[(1, 10), (2, 20), (3, None), (4, None)]
[(1, 10), (2, 20), (3, 0), (4, 0)]
長さチェックで意図しない欠落を防ぐ
長さ不一致が不正な状態なら、明示的に検知します。
早めに失敗させるとバグ原因が特定しやすくなります。
def safe_zip(*iterables):
# すべての長さが同じかを検証し、違えば例外を出す
lengths = [len(it) for it in iterables]
if len(set(lengths)) != 1:
raise ValueError(f"Length mismatch: {lengths}")
return zip(*iterables)
# OK
print(list(safe_zip([1, 2, 3], ["a", "b", "c"])))
# NG -> 例外
try:
print(list(safe_zip([1, 2, 3], ["a"])))
except ValueError as e:
print("Error:", e)
[(1, 'a'), (2, 'b'), (3, 'c')]
Error: Length mismatch: [3, 1]
応用テクニックとアンパッキング
zip(*iterables)でアンパック(行列の入れ替え)
zipはアンパックと組み合わせると行列の転置のような操作が簡単です。
# 2x3の行列を3x2に転置する
rows = [
[1, 2, 3],
[4, 5, 6],
]
# rowsの各行をアンパックし、列単位で束ねる
cols = list(zip(*rows))
print(cols)
# タプルではなくリストが欲しければ内包表記で変換
cols_as_lists = [list(col) for col in zip(*rows)]
print(cols_as_lists)
[(1, 4), (2, 5), (3, 6)]
[[1, 4], [2, 5], [3, 6]]
列ごとの集計や結合に使う
列方向の集計も簡潔です。
# 各列の合計を求める
rows = [
(1, 2, 3),
(10, 20, 30),
(100, 200, 300),
]
col_sums = [sum(col) for col in zip(*rows)]
print(col_sums)
# 2系列の文字列を連結
first = ["Alice", "Bob", "Carol"]
last = ["Smith", "Ito", "Tanaka"]
full = [f"{f} {l}" for f, l in zip(first, last)]
print(full)
[111, 222, 333]
['Alice Smith', 'Bob Ito', 'Carol Tanaka']
リスト化の是非とメモリ注意点
巨大データではlist(zip(...))で一括実体化しないほうが安全です。
zipは遅延評価なので、forループでそのまま消費すればメモリを節約できます。
どうしても複数回使う必要があるなら、itertools.teeで複製する手もありますが、内部バッファを持つため大きすぎるデータには注意してください。
from itertools import tee
pairs = zip(range(10_000_000), range(10_000_000)) # 大量データ
# 直接ループで消費するのが省メモリ
total = 0
for i, j in pairs:
total += i + j
print(total)
# 再利用が必要な場合はteeでイテレータを複製
pairs1, pairs2 = tee(zip([1, 2, 3], [4, 5, 6]), 2)
print(list(pairs1)) # 1回目
print(list(pairs2)) # 2回目
99999990000000
[(1, 4), (2, 5), (3, 6)]
[(1, 4), (2, 5), (3, 6)]
可読性を保つ変数名とコメント
読みやすさはバグの予防線です。
対応する要素は単数形、コレクションは複数形で命名するとzipが自然に読めます。
PEP8に沿って短すぎず長すぎない名前を選び、意図が曖昧になりそうな箇所は行内コメントで補います。
# 悪い例: 意味のない名前
a = ["Alice", "Bob"]
b = [24, 19]
for x, y in zip(a, b):
print(x, y)
# 良い例: 単数・複数の対比が明確
names = ["Alice", "Bob"]
ages = [24, 19]
for name, age in zip(names, ages): # nameとageは要素(単数)
print(f"{name} ({age})") # 読む人に役割が伝わる
Alice 24
Bob 19
Alice (24)
Bob (19)
ここまでの要点を一覧化します。
| 機能 | 主な用途 | 注意点 |
|---|---|---|
| zip | 複数系列の並行走査 | 短い方で切り捨て、イテレータで一度きり |
| itertools.zip_longest | 欠損を埋めつつ並行走査 | fillvalueの選定、None伝播に注意 |
| enumerate | インデックス付与 | 要素自体は1系列、zipとは役割が違う |
| zip(*rows) | 転置・列操作 | 入れ子構造の形に注意(タプルになる) |
まとめ
zipは「対応する要素を安全に同時処理する」ための第一選択肢です。
基本のfor name, age in zip(...)から始め、内包表記、dict生成、enumerate併用、転置のzip(*rows)まで使いこなすと、コードは短く明快になります。
長さ不一致では黙って切り捨てが起こるため、zip_longestや長さチェックを適宜併用してください。
最後に、イテレータの性質(一度きり、遅延評価)を理解してメモリに配慮すれば、大規模データでも安定して扱えるようになります。
