閉じる

Pythonのzip()で複数リストを同時ループする基本と実践

Pythonで複数のリストを同時に処理したいときに頼れるのがzipです。

名前と年齢のような対応する要素をペアにしながら安全に走査でき、コードの可読性と保守性が高まります。

本記事では基本から実践パターン、長さ不一致の対処、アンパッキングまでを段階的に解説します。

zip()の基本: 複数リストを同時ループ

zipの使い方とforループ

zipは複数のイテラブル(リスト、タプル、文字列など)を要素ごとに束ねます。

束ねた結果はタプルの並びとして扱えます。

対応関係のある2つのリストを同時に処理する基本例です。

Python
# 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つ以上にも対応します。

要素数が揃っていれば素直に同時ループできます。

Python
# 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イテレータ(遅延評価)です。

必要になるまで要素を生成しないため省メモリです。

反復すると使い切られる点に注意します。

Python
# 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へ変換してから使います

実践: Pythonのzip()でよく使うパターン

2つのリストを並行処理

スコアを合算するなど、対応要素の演算に適しています。

Python
# 要素ごとの合算 (ベクトル加算のイメージ)
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つ以上のリストを同時ループ

複数属性を扱うときも読みやすく書けます。

Python
# 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つの系列から辞書を素早く作れます。

Python
# キーと値のリストから辞書を作る
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は複数系列を束ねます。

両者は役割が異なり、併用するとインデックス付きの並行走査ができます。

Python
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で一行処理

リストや辞書の内包表記と組み合わせると簡潔に書けます。

Python
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は最短のイテラブルまでで止まります

余った要素は黙って無視されます。

Python
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で埋められます。

Python
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)]

長さチェックで意図しない欠落を防ぐ

長さ不一致が不正な状態なら、明示的に検知します。

早めに失敗させるとバグ原因が特定しやすくなります。

Python
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はアンパックと組み合わせると行列の転置のような操作が簡単です。

Python
# 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]]

列ごとの集計や結合に使う

列方向の集計も簡潔です。

Python
# 各列の合計を求める
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で複製する手もありますが、内部バッファを持つため大きすぎるデータには注意してください。

Python
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に沿って短すぎず長すぎない名前を選び、意図が曖昧になりそうな箇所は行内コメントで補います。

Python
# 悪い例: 意味のない名前
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や長さチェックを適宜併用してください。

最後に、イテレータの性質(一度きり、遅延評価)を理解してメモリに配慮すれば、大規模データでも安定して扱えるようになります。

Python 実践TIPS - コーディング効率化・Pythonic
この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!