複数のリストを同じ順序で見比べながら処理したい場面は、実務でも学習でも非常によくあります。
Pythonには、そのための標準機能としてzip()
が用意されています。
本記事では、zip()
の基本から落とし穴の回避、実践的な応用までを段階的に丁寧に解説します。
Pythonのzip()とは何か
複数リストを同時ループする目的
2つ以上のシーケンス(例: リスト、タプル、文字列)において、同じ位置の要素同士をまとめて扱うと理解しやすく、コードも簡潔になります。
例えば、names
とscores
を同時に取り出せれば、成績表の出力や、複数列のデータ加工を自然な形で書けます。
基本文法 zip(a, b)
zip(a, b)
は、各イテラブルの同じインデックスにある要素を1組のタプルにまとめて順に返します。
最短のイテラブルの長さに合わせて止まるのが特徴です。
# 基本のzip
a = [1, 2, 3]
b = ["x", "y", "z"]
paired = zip(a, b) # ここではイテレータが返る
print(list(paired)) # 確認のためにリストに展開
[(1, 'x'), (2, 'y'), (3, 'z')]
戻り値はイテレータ
Python 3のzip()
はイテレータを返します。
したがって、一度消費すると再利用できません。
a = [10, 20]
b = [100, 200]
z = zip(a, b)
print(z) # zipオブジェクト本体の表示
print(list(z)) # ここで中身を消費
print(list(z)) # もう中身は空
<zip object at 0x...>
[(10, 100), (20, 200)]
[]
タプルのアンパックで要素を受け取る
zip()
が返す各要素はタプルなので、ループ内でアンパックすると読みやすくなります。
names = ["Alice", "Bob", "Carol"]
scores = [85, 92, 78]
for name, score in zip(names, scores): # タプルのアンパック
print(f"{name}: {score}")
Alice: 85
Bob: 92
Carol: 78
Pythonでの基本の同時ループ
2つのリストを同時ループ
最も基本的なパターンです。
2列を見比べながら処理できます。
products = ["Pen", "Notebook", "Eraser"]
prices = [120, 280, 80]
for product, price in zip(products, prices):
print(f"{product} は {price} 円です")
Pen は 120 円です
Notebook は 280 円です
Eraser は 80 円です
3つ以上のリストを同時ループ
3列以上でも同様に列挙できます。
使い方は2つのときと同じです。
names = ["Alice", "Bob", "Carol"]
scores = [85, 92, 78]
grades = ["B", "A", "C"]
for name, score, grade in zip(names, scores, grades):
print(f"{name}: {score} 点 (評価 {grade})")
Alice: 85 点 (評価 B)
Bob: 92 点 (評価 A)
Carol: 78 点 (評価 C)
enumerateとzipでインデックスも扱う
インデックスを併せて使うなら、enumerate(zip(...), start=1)
が簡潔です。
names = ["Alice", "Bob", "Carol"]
scores = [85, 92, 78]
for idx, (name, score) in enumerate(zip(names, scores), start=1):
print(f"{idx}行目 -> {name}: {score}")
1行目 -> Alice: 85
2行目 -> Bob: 92
3行目 -> Carol: 78
listで展開して挙動を確認
zip()
は最短のイテラブルに合わせて止まります。
list()
で展開すると動作確認が容易です。
print(list(zip(range(5), "abc"))) # 文字列"abc"の長さに合わせて止まる
[(0, 'a'), (1, 'b'), (2, 'c')]
実践テクニックと応用パターン
長さが異なるリストを安全に処理
「気づかないうちに余りが落ちる」事故を避けるには、2つの方法があります。
- 厳密に長さ一致を要求する:
zip(..., strict=True)
を使う(Python 3.10+) - 足りない要素を埋めながら処理する:
itertools.zip_longest()
を使う
まずはstrict=True
です。
長さが異なると例外を投げ、バグを早期に発見できます。
a = [1, 2, 3]
b = ["x", "y"]
try:
for x, y in zip(a, b, strict=True): # 長さが異なるとValueError
print(x, y)
except ValueError as e:
print("エラー:", e)
エラー: zip() argument 2 is shorter than argument 1
itertools.zip_longestの使いどころ
異なる長さの列を「欠損埋め」で最後まで処理したい場合に有効です。
fillvalue
で埋める値を指定できます。
from itertools import zip_longest
left = ["A", "B", "C", "D"]
right = [10, 20]
for l, r in zip_longest(left, right, fillvalue=None):
print(f"{l} -> {r}")
A -> 10
B -> 20
C -> None
D -> None
Noneではなく空文字や0で埋めたい場合はfillvalue=""
やfillvalue=0
を選びます。
zip(*iterables)で転置
zip()
は「列と行の入れ替え(転置)」にも使えます。
*
でアンパックして渡すのがポイントです。
# 2x3の「行列」を転置して3x2にする
matrix = [
[1, 2, 3],
[4, 5, 6],
]
transposed = list(zip(*matrix)) # 各列がタプルで得られる
print(transposed)
[(1, 4), (2, 5), (3, 6)]
また、ペアの「逆zip(アンジップ)」も可能です。
pairs = [("a", 1), ("b", 2), ("c", 3)]
keys, values = zip(*pairs) # 2つのシーケンスに分解
print(list(keys), list(values))
['a', 'b', 'c'] [1, 2, 3]
dict(zip(keys, values))でマッピングを作成
zip()
は辞書作成と相性が良く、キー列と値列から辞書を組み立てられます。
keys = ["id", "name", "score"]
values = [101, "Alice", 92]
record = dict(zip(keys, values))
print(record)
{'id': 101, 'name': 'Alice', 'score': 92}
同じキーが重複していると、後勝ちになります。
keys = ["a", "a", "b"]
values = [1, 2, 3]
print(dict(zip(keys, values))) # 'a'は最後の2が採用される
{'a': 2, 'b': 3}
参考として、長さの違いに関する代表的な選択肢を比較しておきます。
手法 | 挙動(長さが違う場合) | 用途の目安 |
---|---|---|
zip(a, b) | 短い方に合わせて静かに切り捨て | 余りを無視してよいとき |
zip(a, b, strict=True) | ValueErrorで即時に気づける | 長さ不一致はバグとみなしたいとき |
itertools.zip_longest(…) | 欠損をfillvalue で埋めて最後まで処理 | 足りない側を埋めつつ全件処理したいとき |
初心者が知っておくべき注意点
余った要素が無視される
zip()
は最短のシーケンスに合わせて止まるため、余りは静かに捨てられます。
想定外のデータ欠落に注意しましょう。
a = [1, 2, 3, 4]
b = ["x", "y"]
print(list(zip(a, b))) # 3と4に対応する要素はなく、結果に含まれない
[(1, 'x'), (2, 'y')]
zipは一度きりのイテレータ
消費すると再利用できません。
複数回使う必要があるなら、list()
で一度展開して使い回すか、再度zip()
を作り直します。
a = [1, 2]
b = [10, 20]
z = zip(a, b)
print(list(z)) # ここで消費
# 再利用したいなら、もう一度zipを作る
z = zip(a, b)
print(list(z))
[(1, 10), (2, 20)]
[(1, 10), (2, 20)]
変数名と順序で可読性を上げる
アンパック時の変数名は「何が入るか」を端的に表すと読みやすくなります。
また、zip()
に渡す引数の順序とアンパックの順序を一致させると誤りを防げます。
# 悪い例: 意味が曖昧
for a, b in zip(names, scores):
...
# 良い例: 役割が明確
for name, score in zip(names, scores):
...
過剰な入れ子を避ける
zip()
を重ねすぎると可読性が落ちます。
中間変数で段階を分けると理解しやすくなります。
# 過剰な入れ子は避ける
# for (n1, s1), (n2, s2) in zip(zip(names1, scores1), zip(names2, scores2)):
# 中間結果を分けて読みやすく
pairs1 = zip(names1, scores1)
pairs2 = zip(names2, scores2)
for (name1, score1), (name2, score2) in zip(pairs1, pairs2):
print(name1, score1, "vs", name2, score2)
# names1やnames2等に応じた出力が行われます(ここでは省略)
まとめ
zip()
は、複数のシーケンスを同時に扱うための基本かつ強力な道具です。
タプルのアンパックと組み合わせることで、直感的でPythonicなループが書けます。
長さが異なる場合の扱いは要点で、切り捨てで良いのか、厳密チェック(strict=True
)すべきか、欠損埋め(itertools.zip_longest
)で最後まで処理するのかを状況に応じて選びましょう。
さらに、転置(zip(*iterables)
)や辞書生成(dict(zip(...))
)などの応用も覚えると、データ処理の幅がぐっと広がります。
まずは基本の2列ループから始め、少しずつ実践的なテクニックを取り入れていくと良いです。