3.

Python の for 文|range・enumerate・zip と while の使い分け

編集
この記事の要点
  • for x in ...:並んでいるものを 1 個ずつ取り出す構文。C 言語風の 3 つ組の for は無い
  • 決まった回数だけ回すなら for i in range(n)range末尾を含まない
  • 番号も欲しいなら enumerate()、2 つのリストを同時に回すなら zip()
  • ループ中にリスト本体を変更しない。要素が飛ばされる。新しいリストを作る
  • 多重ループを一気に抜けたいときは、関数にして return するのが最も簡単

基本形

for name in ["田中", "鈴木", "佐藤"]:
    print(name)

for ch in "abc":                    # 文字列は 1 文字ずつ
    print(ch)

for key in {"a": 1, "b": 2}:        # 辞書はキーが取れる
    print(key)

for key, value in {"a": 1, "b": 2}.items():
    print(key, value)

with open("data.txt", encoding="utf-8") as f:
    for line in f:                  # ファイルは 1 行ずつ
        print(line.rstrip())

リスト・タプル・文字列・辞書・集合・ファイル・ジェネレータなど、順に取り出せるもの(イテラブル)ならすべて for で回せます。

回数を指定する range

for i in range(3):
    print(i)              # 0 1 2      ← 3 は含まれない

for i in range(1, 4):
    print(i)              # 1 2 3

for i in range(0, 10, 2):
    print(i)              # 0 2 4 6 8  第 3 引数は増分

for i in range(3, 0, -1):
    print(i)              # 3 2 1      逆順

print(list(range(5)))     # [0, 1, 2, 3, 4]   中身を見たいときは list()
print(len(range(5)))      # 5

range は数値のリストを作らず必要になった時点で 1 個ずつ生成します。range(10_000_000) でもメモリをほとんど使いません。

番号付き・複数同時のループ

names = ["田中", "鈴木", "佐藤"]

# 添字を自分で管理しない
for i, name in enumerate(names):
    print(i, name)                  # 0 田中 / 1 鈴木 / 2 佐藤

for i, name in enumerate(names, start=1):
    print(f"{i}人目: {name}")        # 1人目: 田中 ...

# 2 つのリストを同時に
ages = [30, 25, 40]
for name, age in zip(names, ages):
    print(name, age)

# 長さが違うと短いほうで止まる。揃っているか確かめたいなら strict=True
for name, age in zip(names, [30, 25], strict=False):
    print(name, age)                # 2 組しか回らない

# 逆順・ソート順
for name in reversed(names):
    print(name)
for name in sorted(names):
    print(name)

for i in range(len(names)) と書いて names[i] で取り出すのは、Python では避けたい書き方です。enumerate()zip() でほぼ置き換えられます。

ループ中にリストを変更しない

nums = [1, 2, 3, 4, 5, 6]

# 危険: 削除すると後ろがずれて要素が飛ばされる
for n in nums:
    if n % 2 == 0:
        nums.remove(n)
print(nums)              # [1, 3, 5] にならず [1, 3, 5] 以外になることがある

# 正しい: 新しいリストを作る
nums = [1, 2, 3, 4, 5, 6]
nums = [n for n in nums if n % 2 != 0]
print(nums)              # [1, 3, 5]

# その場で書き換えたい場合はスライス代入
data = [1, 2, 3, 4]
data[:] = [n for n in data if n != 2]
print(data)              # [1, 3, 4]

辞書や集合の場合はさらに厳しく、ループ中にサイズを変えると RuntimeError: dictionary changed size during iteration になります。キーの一覧を先に list() で確定させてから回します。

多重ループの抜け方

matrix = [[1, 2], [3, 4], [5, 6]]

# break は内側の 1 段しか抜けない
for row in matrix:
    for v in row:
        if v == 4:
            break        # 内側の for を抜けるだけ

# 方法 1: 関数にして return(最も分かりやすい)
def find(matrix, target):
    for y, row in enumerate(matrix):
        for x, v in enumerate(row):
            if v == target:
                return y, x
    return None

print(find(matrix, 4))   # (1, 1)

# 方法 2: for...else で外側にも break を伝える
for row in matrix:
    for v in row:
        if v == 4:
            found = v
            break
    else:
        continue         # 内側が break しなかったら次の行へ
    break                # 内側が break したらここに来て外側も抜ける

Python にラベル付き break はありません。迷ったら関数に切り出して return が最も読みやすくなります。

for と while の使い分け

やりたいこと書き方
要素を順に処理for x in items:
n 回くり返すfor i in range(n):
2 つを同時にfor a, b in zip(xs, ys):
条件を満たす間(回数不明)while 条件:
1 回はやってから判断while True:break

回数や対象が最初から決まっているなら for、決まっていないなら while と覚えると迷いません。

ループでよく使う組み込み関数

関数用途
enumerate(x)番号付きで取り出す
zip(a, b)複数を同時に取り出す
reversed(x)逆順
sorted(x, key=...)並べ替えてから回す
sum() / max() / min()集計。ループを書かずに済む
any() / all()条件を満たすものがあるか / すべてか
users = [{"name": "田中", "age": 30}, {"name": "鈴木", "age": 25}]

for u in sorted(users, key=lambda u: u["age"]):
    print(u["name"])                     # 鈴木 → 田中

print(sum(u["age"] for u in users))      # 55
print(max(users, key=lambda u: u["age"])["name"])   # 田中
print(any(u["age"] >= 30 for u in users))          # True

# 見つかった時点で止まる(全件回さない)
adult = next((u for u in users if u["age"] >= 30), None)
print(adult)                             # {'name': '田中', 'age': 30}

next(..., None) は「条件に合う最初の 1 件」を取り出す書き方です。見つかった時点で評価が止まるので、大きなリストでも無駄がありません。

大きなデータを扱うとき

# リスト内包表記は全件をメモリに載せる
squares = [n * n for n in range(10_000_000)]     # 数百 MB

# ジェネレータ式なら 1 件ずつ作る
squares = (n * n for n in range(10_000_000))     # ほぼ 0
print(sum(squares))

# 巨大ファイルも 1 行ずつなら安全
with open("huge.csv", encoding="utf-8") as f:
    total = sum(1 for _ in f)     # 行数を数えるだけならこれで足りる

かっこを [ ] から ( ) に変えるだけでその場で作らない(遅延評価)になります。ただしジェネレータは一度しか回せません。2 回使うならリストにしてください。

itertools を使う場面

from itertools import chain, combinations, groupby, islice, product

print(list(chain([1, 2], [3, 4])))          # [1, 2, 3, 4]   連結
print(list(islice(range(100), 5)))          # [0, 1, 2, 3, 4] 先頭 5 件
print(list(product("ab", "12")))            # 総当たり(二重ループの代わり)
print(list(combinations([1, 2, 3], 2)))     # [(1, 2), (1, 3), (2, 3)]

# 同じキーごとにまとめる(先にソートしておくこと)
rows = [("A", 1), ("A", 2), ("B", 3)]
for key, group in groupby(rows, key=lambda r: r[0]):
    print(key, [v for _, v in group])       # A [1, 2] / B [3]

groupby()連続した同じキーだけをまとめます。SQL の GROUP BY と違い、先に sorted() しないと同じキーが分かれてしまいます。

関連

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 条件分岐 (if文)
  2. 繰り返し処理(while文)
  3. 繰り返し処理
  4. 繰り返しの中断 (break)
  5. 繰り返しのスキップ (continue)

最近更新/作成されたページ