メインコンテンツに移動

Pythonループ(for・while)入門|繰り返し処理の書き方・range・breakまで解説

Pythonで同じ処理を何度も実行したいとき、一行ずつ同じコードを書く必要はありません。繰り返し処理を使えば、リストに入っている全商品を表示する、1から100までの数値を合計する、利用者が正しい値を入力するまで質問を続ける、といった処理を簡潔に記述できます。

Pythonの代表的な繰り返し構文はfor文とwhile文です。for文はリストや文字列などから要素を順番に取り出す処理に向き、while文は条件が成立している間、処理を繰り返す場合に利用します。Pythonのfor文は、数値カウンターだけを操作するのではなく、イテラブルから要素を順番に取り出す構文として設計されています。

本記事では、forwhileの書き方だけでなく、range()enumerate()zip()breakcontinue、ループのelse、入れ子、内包表記、イテレータまで詳しく解説します。最初はコードをそのまま実行し、その後で値や条件を変更して結果の違いを確認してください。

1. Pythonのループとは

ループとは、同じ処理または似た処理を、指定した回数や条件に従って繰り返す仕組みです。Pythonでは主にfor文とwhile文を使い、処理対象や終了条件に応じて使い分けます。

1.1 ループとは

ループは、複数の値に対して同じ処理を適用したり、条件を満たすまで処理を続けたりするために使います。同じコードを何行もコピーする方法と比べて、処理内容を変更しやすく、入力データの件数が変わっても対応できます。

例えば、5人の名前を表示する処理を手作業で5回書くと、人数が100人になったときに修正が必要です。リストとfor文を使えば、リストの要素数にかかわらず同じコードで処理できます。

コード例

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

for name in names:
   print(name)
 

このコードでは、namesから名前を一つずつ取り出し、変数nameへ代入して表示しています。

1.2 for文とwhile文の役割

for文は、文字列、リスト、タプル、辞書などのイテラブルから、要素を順番に取り出すときに使います。while文は、指定した条件式が真である間、処理を繰り返します。

処理回数や対象データが明確であればfor文、終了時点が条件によって決まるならwhile文が適しています。どちらでも書ける処理はありますが、目的を自然に表現できる構文を選ぶことが重要です。

比較項目for文while文
主な用途要素を順番に処理する条件が真の間続ける
終了時点要素を取り出し終えたとき条件が偽になったとき
向く処理一覧、回数指定、集計入力待ち、再試行、状態監視
主な注意点対象データの変更無限ループ
コードの読みやすさ件数処理を表現しやすい条件処理を表現しやすい

1.3 インデントの役割

Pythonでは、ループの対象となる処理をインデントで表します。forまたはwhileの行はコロンで終わり、次の行から字下げした処理がループ本体になります。複合文のヘッダーはコロンで終わり、その下のインデントされたまとまりが実行対象になります。

インデントが終了すると、ループの外側へ戻ります。空白の位置が処理の所属を決めるため、見た目をそろえるだけでなく、どの処理を繰り返したいのか意識してください。

コード例

numbers = [1, 2, 3]

for number in numbers:
   doubled = number * 2
   print(doubled)

print("ループが終了しました")
 

doubledの計算と表示は3回実行されますが、最後のメッセージはインデントされていないため1回だけ実行されます。

1.4 イテラブルとは

イテラブルとは、要素を一つずつ順番に取り出せるオブジェクトです。リスト、タプル、文字列、辞書、集合、rangeオブジェクト、ファイルなどが代表例で、for文は対象からイテレータを作成し、要素がなくなるまで処理します。

すべてのPythonオブジェクトをfor文へ直接渡せるわけではありません。整数のように、そのままでは複数の要素を持たない値を繰り返す場合は、range()などを使ってイテラブルを作ります。

for character in "Python":    print(character)

文字列もイテラブルであるため、一文字ずつ取り出せます。

1.5 forとwhileの選び方

「データを一件ずつ処理する」という目的なら、通常はfor文を選びます。「条件を満たすまで続ける」という目的なら、while文を選びます。Python公式の言語リファレンスでも、forはイテラブルの要素を処理し、whileは条件式が真の間繰り返す構文として定義されています。

カウンター変数だけを使ったwhile文でリストを処理することも可能ですが、位置番号の更新忘れや範囲外アクセスが起こりやすくなります。リストの要素そのものが必要な場合は、for文を優先してください。

処理内容推奨する構文
リストの全要素を表示するfor
10回繰り返すforrange()
正しい値が入力されるまで続けるwhile
ファイルを一行ずつ読むfor
条件を満たすまで再試行するwhile

2. for文の使い方

for文は、イテラブルに含まれる要素を順番に変数へ代入し、ブロック内の処理を実行します。対象の要素を取り出し終えると、自動的にループが終了します。

2.1 リストを繰り返す

リストをfor文へ渡すと、先頭の要素から順番に取り出されます。位置番号を管理せず、要素そのものを変数として利用できます。

商品の一覧表示、得点の合計、利用者データの検証など、複数の同種データを処理する場面で最もよく使われる書き方です。

コード例

prices = [1200, 2500, 800, 3200]

total = 0

for price in prices:
   total += price

print(f"合計金額: {total}円")
 

ループのたびにpriceへ一つの価格が入り、totalへ加算されます。

2.2 文字列を繰り返す

文字列をfor文へ渡すと、一文字ずつ取り出せます。日本語、英字、数字、記号を含む文字列でも同じ構文を利用できます。

文字数の集計、特定文字の検索、文字列の変換などに使えます。ただし、文字列は変更できない型なので、文字を直接書き換えるのではなく、新しい文字列を作成します。

text = "Python入門"

for character in text:
   print(character)
 

特定の文字を数える例です。

text = "banana"
count = 0

for character in text:
   if character == "a":
       count += 1

print(count)
 

2.3 タプルを繰り返す

タプルもリストと同様に、要素を先頭から順番に処理できます。タプルは作成後に要素を追加、削除、変更できませんが、ループによる読み取りは可能です。

座標、色の成分、固定された設定値など、変更しない値の組を処理するときに使われます。

coordinates = (
   (10, 20),
   (30, 40),
   (50, 60),
)

for coordinate in coordinates:
   print(coordinate)
 

タプルの要素を分割して受け取ることもできます。

for x, y in coordinates:
   print(f"x={x}, y={y}")
 

2.4 集合を繰り返す

集合もイテラブルなので、for文で要素を取り出せます。ただし、集合は順序による位置管理を目的とするデータ型ではないため、表示順や処理順へ依存するコードを書かないようにします。

集合は、重複しない値の所属確認や集合演算に向いています。一定の順序で処理したい場合は、sorted()で並べ替えてから繰り返します。

roles = {
   "admin",
   "editor",
   "viewer",
}

for role in roles:
   print(role)
 

順番を安定させる例です。

for role in sorted(roles):
   print(role)
 

2.5 辞書を繰り返す

辞書をそのままfor文へ渡すと、キーが順番に取り出されます。キーと値の両方が必要ならitems()、値だけが必要ならvalues()を使います。

辞書は挿入順序を保持しますが、キーの名前や値の大きさで自動的に並べ替えられるわけではありません。並び替えが必要ならsorted()を組み合わせます。

scores = {
   "田中": 85,
   "佐藤": 72,
   "鈴木": 91,
}

for name, score in scores.items():
   print(f"{name}: {score}点")

書き方取り出す内容
for key in dataキー
for key in data.keys()キー
for value in data.values()
for key, value in data.items()キーと値

3. range関数で回数を指定する

range()は、整数の等差数列を表す変更不可能なシーケンスを作成します。for文と組み合わせることで、指定回数の繰り返しや連続した数値の処理を記述できます。

3.1 rangeの終了値だけを指定する

range(stop)と書くと、0からstop未満までの整数を生成します。range(5)の場合、0、1、2、3、4が順番に取り出され、5は含まれません。

指定した回数だけ処理したい場合に利用できます。ループ変数を使わない場合は、慣例的に_という名前を付けることがあります。

for number in range(5):
   print(number)
 

5回メッセージを表示する例です。

for _ in range(5):
   print("処理を実行します")
 

3.2 開始値と終了値を指定する

range(start, stop)では、開始値から終了値未満までの整数を生成します。range(1, 6)なら、1から5までが対象です。

終了値を含まない点は、初心者が間違えやすい部分です。1から10までを処理したい場合は、終了値へ11を指定します。

for number in range(1, 6):
   print(number)
 

1から100までを合計する例です。

total = 0

for number in range(1, 101):
   total += number

print(total)
 

3.3 増減幅を指定する

range(start, stop, step)では、三番目の引数で数値の増減幅を指定します。stepが2なら2ずつ増え、負数なら減少します。

stepへ0を指定することはできません。数値が変化しないため、ValueErrorが発生します。

for number in range(0, 11, 2):    print(number)

実行結果です。

0 2 4 6 8 10

3.4 逆順に繰り返す

大きな値から小さな値へ処理する場合は、負のstepを指定します。終了値は正方向の場合と同様に含まれません。

開始値より終了値が小さいのにstepが正数の場合、要素は一つも生成されません。方向と増減幅を一致させてください。

for number in range(5, 0, -1):    print(number) print("開始")

実行結果です。

5 4 3 2 1 開始

3.5 rangeとリストの違い

range()の戻り値はリストではなく、range型のオブジェクトです。必要な数値をすべてリストとして保存せず、数値の範囲を表すため、大きな回数のループでも利用しやすい特徴があります。

内容をリストとして確認したい場合は、list()で変換できます。ただし、大きな範囲を無条件にリストへ変換すると、多くのメモリを使用します。

numbers = range(1, 6)

print(numbers)
print(type(numbers))
print(list(numbers))

内容
range(5)range0から4の範囲
list(range(5))list[0, 1, 2, 3, 4]
range(1, 6)range1から5の範囲
range(10, 0, -2)range10、8、6、4、2

4. enumerate・zip・reversed・sortedの使い方

Pythonには、ループ処理を読みやすくする組み込み関数があります。位置番号にはenumerate()、複数データの同時処理にはzip()、逆順にはreversed()、並べ替えにはsorted()を利用できます。

4.1 enumerateで位置番号を取得する

enumerate()は、イテラブルの要素と連番を組み合わせたイテレータを返します。連番は標準では0から始まり、startを指定すると開始値を変更できます。

range(len(values))と位置番号アクセスを組み合わせるより、要素と番号を直接受け取れるため読みやすくなります。

languages = [
   "Python",
   "Java",
   "Go",
]

for index, language in enumerate(languages):
   print(index, language)
 

1から番号を付ける例です。

for number, language in enumerate(    languages,    start=1, ):    print(f"{number}. {language}")

4.2 zipで複数のデータを処理する

zip()は、複数のイテラブルから同じ位置の要素を取り出し、タプルとしてまとめたイテレータを返します。名前と得点、商品と価格など、対応するデータを同時に処理できます。

通常のzip()では、入力の長さが異なる場合、最も短いイテラブルを使い切った時点で終了します。この動作によって不足データを見逃す可能性があるため、長さが一致すべきデータでは確認が必要です。

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

scores = [
   85,
   72,
   91,
]

for name, score in zip(names, scores):
   print(f"{name}: {score}点")
 

4.3 zipのstrictで長さを検証する

zip(..., strict=True)を使うと、入力イテラブルの長さが異なる場合にValueErrorを発生させられます。データ数が必ず一致しなければならない処理で、不足や余分な要素を早期に発見できます。

長さが違っていても短い側で終了してよい場合は、標準のzip()を使います。欠けた部分を既定値で補いたい場合は、itertools.zip_longest()が候補になります。

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

scores = [
   85,
   72,
   91,
]

for name, score in zip(
   names,
   scores,
   strict=True,
):
   print(name, score)
 

この例では要素数が異なるため、ValueErrorが発生します。

4.4 reversedで逆順に処理する

reversed()は、対応するシーケンスを逆方向に処理するイテレータを返します。元のリスト自体を変更せず、逆順に繰り返せます。

リストのreverse()は元のリストを直接変更しますが、reversed()は逆順イテレータを返します。元データを保持したい場合に適しています。

numbers = [1, 2, 3, 4, 5]

for number in reversed(numbers):
   print(number)

print(numbers)

方法元データを変更戻り値・結果
reversed(data)しない逆順イテレータ
data.reverse()するNone
data[::-1]しない新しいシーケンス

4.5 sortedで並べ替えて処理する

sorted()は、イテラブルの要素を並べ替えた新しいリストを返します。keyで並べ替えの基準、reverse=Trueで降順を指定できます。

辞書の値、文字列の長さ、オブジェクトの属性など、単純な大小比較以外の基準でも並べ替えられます。

products = [
   {
       "name": "ノート",
       "price": 200,
   },
   {
       "name": "ペン",
       "price": 100,
   },
   {
       "name": "ファイル",
       "price": 350,
   },
]

for product in sorted(
   products,
   key=lambda item: item["price"],
):
   print(
       product["name"],
       product["price"],
   )
 

5. while文の使い方

while文は、条件式が真である間、ブロック内の処理を繰り返します。条件は各ループの開始前に評価され、最初から偽であれば本体は一度も実行されません。

5.1 while文の書き方

基本形はwhile 条件:です。ループ内で条件に関係する変数を更新し、いつか条件が偽になるようにします。

終了条件が変化しないコードを書くと、処理が終わらない無限ループになります。条件式と更新処理をセットで確認してください。

count = 1

while count <= 5:
   print(count)
   count += 1
 

このコードでは、countが6になると条件が偽になり、ループが終了します。

5.2 カウンターを使って繰り返す

カウンター変数を増減させることで、指定回数のwhileループを作れます。ただし、回数が明確な処理ではforrange()のほうが簡潔な場合が多くなります。

whileを選ぶ場合は、初期値、条件式、更新式の三つを確認してください。一つでも間違えると、実行回数のずれや無限ループが発生します。

count = 0

while count < 3:
   print("処理中")
   count += 1

要素この例の値
初期値count = 0
継続条件count < 3
更新count += 1
実行回数3回

5.3 正しい入力まで繰り返す

利用者が正しい値を入力するまで質問を続ける処理は、終了回数が事前に決まらないためwhile文に向いています。

入力値を確認し、正しければbreakで終了する方法や、条件変数を更新する方法があります。無限ループとbreakを使う場合は、終了条件が明確に存在するか確認してください。

while True:
   text = input(
       "1から10の整数を入力してください: "
   )

   if not text.isdigit():
       print("整数を入力してください")
       continue

   number = int(text)

   if 1 <= number <= 10:
       break

   print("範囲外です")

print(f"入力値は{number}です")
 

5.4 番兵値で終了する

番兵値とは、繰り返しを終了させるために決めた特別な値です。例えば、qが入力されたら終了する、負数が入力されたら集計を終える、といった処理です。

通常のデータとして使われる可能性がない値を選ぶ必要があります。0が有効な入力なら、0を終了値にすると利用者が入力できる値を制限してしまいます。

total = 0

while True:
   text = input(
       "金額を入力してください"
       "(終了はq): "
   )

   if text == "q":
       break

   total += int(text)

print(f"合計: {total}円")
 

5.5 forへ置き換えられるwhile

リストを位置番号で処理するwhile文は、for文へ置き換えられることが多くあります。forを使えば、位置番号の初期化と更新が不要です。

位置番号そのものも必要な場合は、enumerate()を使います。単に要素を取り出すだけなら、リストを直接for文へ渡してください。

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

index = 0

while index < len(names):
   print(names[index])
   index += 1
 

より自然な書き方です。

for name in names:    print(name)

6. break・continue・passの使い方

breakはループを終了し、continueは現在の回の残りを飛ばし、次の反復へ進みます。passは何も処理しない文であり、ループを終了したり次の回へ移動したりする機能はありません。

6.1 breakでループを終了する

breakは、その文を含む最も内側のforまたはwhileループを終了します。後続の要素や条件確認は行われません。

目的の値を見つけた時点で探索を終了する処理や、利用者が終了を選択した処理で利用できます。

numbers = [
   4,
   7,
   12,
   15,
]

for number in numbers:
   if number % 2 == 0 and number > 10:
       print(f"見つかりました: {number}")
       break
 

6.2 continueで現在の回を飛ばす

continueは、現在の反復で残っている処理を実行せず、次の要素または次の条件判定へ進みます。

処理対象外のデータを早めに除外すると、条件分岐の入れ子を浅くできます。ただし、while文では更新処理まで飛ばして無限ループにならないよう注意してください。

numbers = [
   1,
   -2,
   3,
   -4,
   5,
]

for number in numbers:
   if number < 0:
       continue

   print(number)
 

6.3 passで何もしない

passは、構文上は文が必要でも、実行する処理がない場合に使います。実行されても何も起こらず、その次の文へ進みます。

未実装の処理を一時的に空にしたり、特定条件では何もしないことを明示したりできます。continueとは異なり、後続の処理は通常どおり実行されます。

for number in range(5):
   if number == 2:
       pass

   print(number)
 

この例では2も表示されます。

6.4 入れ子ループでのbreak

入れ子になったループでbreakを実行すると、終了するのは最も内側のループだけです。外側のループはそのまま続きます。

二重ループ全体を終了したい場合は、フラグ変数、関数からのreturn、例外などの方法を検討します。処理を関数へ分け、見つかった時点で値を返す方法が読みやすくなりやすいでしょう。

matrix = [
   [1, 2, 3],
   [4, 5, 6],
   [7, 8, 9],
]

for row in matrix:
   for value in row:
       if value == 5:
           print("5を発見")
           break

   print("外側のループは続きます")
 

6.5 三つの文の違い

breakcontinuepassは見た目が短いため混同しやすいものの、処理の流れは大きく異なります。

特にpassを「次のループへ進む文」と誤解しないよう注意してください。何もしないだけなので、同じブロックの後続コードは実行されます。

現在の処理次の反復ループ全体
break中止行わない終了
continue残りを中止行う継続
pass何もしない通常どおり継続
return関数を終了行わない関数ごと終了

7. ループのelse節

Pythonのfor文とwhile文にはelse節を付けられます。forが要素を使い切った場合、またはwhileの条件が偽になった場合に実行され、breakで終了した場合には実行されません。

7.1 for文のelse

forelseは、イテラブルの要素を最後まで処理し、breakされなかった場合に実行されます。

検索処理で対象が見つからなかった場合の処理を記述できます。elseifではなく、forと同じインデント位置に書きます。

numbers = [
   3,
   7,
   9,
   11,
]

for number in numbers:
   if number % 2 == 0:
       print(
           f"偶数を発見: {number}"
       )
       break
else:
   print("偶数はありません")
 

7.2 while文のelse

whileelseは、条件式が偽になって通常終了した場合に実行されます。breakで終了した場合は実行されません。

一定回数の試行を最後まで行った場合の処理などに利用できます。ただし、初めて読む人には意図が伝わりにくいこともあるため、チームの習慣に合わせます。

count = 1

while count <= 3:
   print(f"{count}回目")
   count += 1
else:
   print("すべて終了しました")
 

7.3 breakとelseの関係

ループのelseを理解するポイントは、「条件が真か偽か」だけでなく、breakによって中断されたかどうかです。

continueではループ全体を中断しないため、最終的に要素を使い切ればelseは実行されます。

終了方法forのelsewhileのelse
要素を使い切る実行該当しない
条件が偽になる該当しない実行
break実行しない実行しない
例外通常は実行しない通常は実行しない
return実行しない実行しない

7.4 検索処理で使用する

ループのelseは、「見つかればbreak、見つからなければelse」という検索処理と相性がよい構文です。

フラグ変数を用意しなくても、見つからなかった場合の処理を表現できます。処理が関数内にあるなら、見つかった時点でreturnする方法も候補です。

users = [
   {
       "id": 1,
       "name": "田中",
   },
   {
       "id": 2,
       "name": "佐藤",
   },
]

target_id = 3

for user in users:
   if user["id"] == target_id:
       print(user["name"])
       break
else:
   print("利用者が見つかりません")
 

7.5 elseを使わない書き方

ループのelseは便利ですが、すべての開発者に馴染みがあるとは限りません。読み手が誤解しやすい場合は、関数とreturn、または検索用の組み込み関数を使う方法もあります。

単純な存在確認ならinany()、条件を満たすすべての値が必要ならリスト内包表記なども検討します。

def find_user(
   users: list[dict],
   target_id: int,
) -> dict | None:
   for user in users:
       if user["id"] == target_id:
           return user

   return None
 

この方法では、見つかった時点で関数を終了し、見つからなければ最後にNoneを返します。

8. 入れ子のループ

ループの中に別のループを書くことを、入れ子またはネストしたループと呼びます。表形式のデータ、組み合わせ、階層データなどを処理できます。

8.1 二次元リストを処理する

リストの中にリストがある二次元データでは、外側のループで行、内側のループで各要素を処理できます。

行数と列数が増えるほど処理回数も増えます。各ループ変数が何を示しているか分かる名前を使ってください。

matrix = [
   [1, 2, 3],
   [4, 5, 6],
   [7, 8, 9],
]

for row in matrix:
   for value in row:
       print(value)
 

行ごとに合計する例です。

for row in matrix:
   total = 0

   for value in row:
       total += value

   print(total)
 

8.2 掛け算表を作る

二つの数値範囲を組み合わせると、掛け算表のような処理を作れます。外側の数値一つに対し、内側のループがすべて実行されます。

外側が9回、内側が9回なら、計算処理は合計81回行われます。

for left in range(1, 10):
   for right in range(1, 10):
       result = left * right
       print(
           f"{left} × {right}"
           f" = {result}"
       )

外側の回数内側の回数本体の実行回数
3412
5525
9981
nmn × m

8.3 辞書とリストを組み合わせる

複数の利用者を表すリストの中に、各利用者の技能リストがあるような階層データも、入れ子ループで処理できます。

データ構造が深くなると、キーや値の存在確認も必要です。外部データではget()や入力検証を利用してください。

users = [
   {
       "name": "田中",
       "skills": [
           "Python",
           "SQL",
       ],
   },
   {
       "name": "佐藤",
       "skills": [
           "Java",
           "Git",
       ],
   },
]

for user in users:
   print(user["name"])

   for skill in user["skills"]:
       print(f"  - {skill}")
 

8.4 すべての組み合わせを作る

二つの一覧からすべての組み合わせを作る場合、入れ子ループを利用できます。色とサイズ、曜日と時間帯などの組み合わせを生成できます。

同じ一覧から重複しない組み合わせを作る場合は、itertools.combinations()のほうが目的を明確に表現できます。

colors = [
   "黒",
   "白",
]

sizes = [
   "S",
   "M",
   "L",
]

for color in colors:
   for size in sizes:
       print(color, size)
 

生成される組み合わせは2 × 3で6通りです。

8.5 入れ子が深すぎる場合

三重、四重とループが深くなると、処理の流れや変数の意味を追いにくくなります。関数へ分割したり、データ構造を変更したりする方法を検討してください。

入れ子が必要でも、各段階で不要なデータを早めに除外すれば、処理回数を減らせることがあります。

def display_skills(
   user: dict,
) -> None:
   for skill in user["skills"]:
       print(f"  - {skill}")


for user in users:
   print(user["name"])
   display_skills(user)
 

9. ループ変数とデータの変更

for文では、各要素がループ変数へ代入されます。変数への再代入と、元データの変更は同じではありません。変更可能なオブジェクトを処理するときは、参照関係にも注意が必要です。

9.1 複数の値を分割代入する

各要素がタプルやリストの場合、ループ変数を複数書いて分割代入できます。

辞書のitems()zip()、座標データなどで頻繁に利用されます。要素数と変数数が一致しなければValueErrorになります。

products = [
   ("ノート", 200),
   ("ペン", 100),
   ("消しゴム", 80),
]

for name, price in products:
   print(f"{name}: {price}円")
 

9.2 アスタリスク付きで残りを受け取る

要素数が三つ以上あり、一部と残りを分けたい場合は、アスタリスク付き変数を利用できます。Pythonのfor文ではターゲットリストへの代入規則が使われます。

アスタリスク付きの変数には、残りの値がリストとして入ります。

records = [
   [
       "田中",
       85,
       72,
       91,
   ],
   [
       "佐藤",
       68,
       77,
       80,
   ],
]

for name, *scores in records:
   print(name, scores)
 

9.3 ループ変数の再代入

ループ内でループ変数へ別の値を代入しても、次の要素を取り出す処理には影響しません。次の反復が始まると、次の要素が再び変数へ代入されます。

リスト内の整数を変更したい場合に、ループ変数へ計算結果を代入するだけでは元のリストは変わりません。

numbers = [
   1,
   2,
   3,
]

for number in numbers:
   number *= 2

print(numbers)
 

実行結果は[1, 2, 3]のままです。

9.4 ループ中にリストを変更する

繰り返し対象のリストへ要素を追加または削除すると、要素を飛ばしたり、予想以上に処理が続いたりする可能性があります。

削除対象を別に集める、コピーを繰り返す、新しいリストを作るなどの方法を選びます。

numbers = [
   1,
   2,
   3,
   4,
   5,
]

filtered = []

for number in numbers:
   if number % 2 != 0:
       filtered.append(number)

print(filtered)
 

内包表記でも書けます。

filtered = [
   number
   for number in numbers
   if number % 2 != 0
]
 

9.5 ループ中に辞書を変更する

辞書を繰り返している途中にキーを追加または削除すると、実行時エラーになることがあります。値だけを更新する場合とは区別してください。

削除するキーを先に一覧化するか、list(dictionary)でキーのコピーを作ってから処理します。

scores = {
   "田中": 85,
   "佐藤": 42,
   "鈴木": 73,
}

for name in list(scores):
   if scores[name] < 60:
       del scores[name]

print(scores)

変更内容ループ中の扱い
変数への再代入元の要素は通常変わらない
辞書の値を更新状況により可能
辞書のキーを追加・削除避ける
リストへ追加・削除避ける
新しいコレクションを作成安全で分かりやすい

10. 内包表記と通常のループ

内包表記は、繰り返し処理からリスト、辞書、集合を作成する構文です。短い変換や絞り込みを簡潔に書けますが、複雑な処理では通常のfor文のほうが読みやすくなります。

10.1 リスト内包表記

リスト内包表記は、各要素を変換して新しいリストを作ります。基本形式は[式 for 要素 in イテラブル]です。

元のリストを直接変更せず、変換後のリストを新しく作ります。

numbers = [
   1,
   2,
   3,
   4,
   5,
]

squares = [
   number ** 2
   for number in numbers
]

print(squares)
 

通常のループでは次のようになります。

squares = []

for number in numbers:
   squares.append(
       number ** 2
   )
 

10.2 条件付きリスト内包表記

for部分の後ろにifを書くと、条件を満たす要素だけを新しいリストへ含められます。

変換と絞り込みが一つずつ程度なら読みやすいものの、複数条件を詰め込みすぎないようにします。

numbers = range(1, 11)

even_squares = [
   number ** 2
   for number in numbers
   if number % 2 == 0
]

print(even_squares)
 

10.3 辞書内包表記

辞書内包表記では、キーと値の式をコロンで区切ります。数値と二乗値の対応表や、既存辞書の変換に利用できます。

キーが重複すると、後から作成された値で上書きされます。

squares = {
   number: number ** 2
   for number in range(1, 6)
}

print(squares)
 

既存の辞書を変換する例です。

prices = {
   "ノート": 200,
   "ペン": 100,
}

tax_included = {
   name: int(price * 1.1)
   for name, price in prices.items()
}
 

10.4 集合内包表記とジェネレータ式

集合内包表記は中括弧を使い、重複しない結果を作ります。ジェネレータ式は丸括弧を使い、値を必要になった時点で順番に生成します。

大量のデータを一度にリストへ保存する必要がない場合は、ジェネレータ式が適することがあります。

words = [
   "Python",
   "python",
   "PYTHON",
   "Java",
]

normalized = {
   word.lower()
   for word in words
}

print(normalized)
 

ジェネレータ式の例です。

squares = (
   number ** 2
   for number in range(1_000_000)
)

for value in squares:
   if value > 100:
       break
 

10.5 通常のfor文を選ぶ場面

複数の処理、例外処理、ログ出力、複雑な条件分岐がある場合は、通常のfor文を使います。

一行で書けることより、処理内容と失敗理由を理解しやすいことを優先してください。

処理向いている書き方
単純な変換内包表記
単純な絞り込み内包表記
複数段階の条件通常のfor
例外処理を含む通常のfor
ログを出力する通常のfor
副作用が中心通常のfor

11. イテラブル・イテレータ・ジェネレータ

Pythonのfor文は、内部でイテレータを利用します。イテラブル、イテレータ、ジェネレータの関係を理解すると、大量データや独自の繰り返し処理を扱いやすくなります。

11.1 イテラブルとイテレータの違い

イテラブルは、イテレータを取得できるオブジェクトです。イテレータは、次の要素を一つずつ返し、要素がなくなったことを通知するオブジェクトです。

リストは何度でも新しいイテレータを作れますが、一つのイテレータは通常、要素を取り出すごとに進みます。

numbers = [
   10,
   20,
   30,
]

iterator = iter(numbers)

print(iterator)
print(next(iterator))
print(next(iterator))
 

11.2 iterとnextを使う

iter()はイテラブルからイテレータを取得し、next()はイテレータから次の要素を一つ取得します。

通常のコードではfor文がこの処理を自動的に行うため、iter()next()を直接使う必要は多くありません。イテレータの仕組みを理解したり、必要な件数だけ手動で取得したりする場合に使います。

iterator = iter(
   ["A", "B", "C"]
)

first = next(iterator)
second = next(iterator)

print(first)
print(second)
 

11.3 StopIteration

イテレータの要素をすべて取り出した後にnext()を呼ぶと、StopIteration例外が発生します。for文は、この終了通知を内部で処理してループを終えます。

next(iterator, default)と第二引数を指定すると、要素がない場合に例外ではなく既定値を返せます。

iterator = iter([1])

print(next(iterator))
print(
   next(
       iterator,
       "終了",
   )
)
 

11.4 yieldでジェネレータを作る

関数内でyieldを使うと、値を一つ返した時点で実行状態を保持し、次回の反復時に続きから処理するジェネレータを作れます。ジェネレータ関数を呼び出すと、すべての処理を一度に実行するのではなく、ジェネレータオブジェクトが返されます。

大量データを一件ずつ生成する処理や、終了条件まで値を作り続ける処理に利用できます。

def countdown(start: int):
   current = start

   while current > 0:
       yield current
       current -= 1


for number in countdown(5):
   print(number)
 

11.5 イテレータは使い切られる

イテレータは、要素を取り出すと位置が進みます。一度最後まで繰り返した同じイテレータを再びfor文へ渡しても、通常は何も取り出せません。

同じデータを再度処理したい場合は、元のイテラブルから新しいイテレータを作成します。

numbers = [
   1,
   2,
   3,
]

iterator = iter(numbers)

for number in iterator:
   print(number)

print("2回目")

for number in iterator:
   print(number)
 

2回目のループでは何も表示されません。

12. itertoolsでループを拡張する

標準ライブラリのitertoolsは、効率的なループ処理に利用できるイテレータ生成関数を提供します。無限イテレータ、連結、切り出し、組み合わせなどを簡潔に表現できます。

12.1 count・cycle・repeat

count()は一定間隔で数値を生成し続け、cycle()はイテラブルの要素を繰り返し、repeat()は同じ値を指定回数または無期限に返します。

count()cycle()は停止条件を指定しないと無限に値を返すため、breakislice()と組み合わせます。

from itertools import count

for number in count(
   start=10,
   step=5,
):
   print(number)

   if number >= 30:
       break
from itertools import cycle

colors = cycle([
   "赤",
   "青",
   "緑",
])

for _ in range(5):
   print(next(colors))
 

12.2 chainで複数データを連結する

chain()は、複数のイテラブルを一つずつ順番に処理できるイテレータを作ります。

リスト同士を+で結合して新しいリストを作らなくても、各要素を連続して処理できます。

from itertools import chain

first = [
   "田中",
   "佐藤",
]

second = [
   "鈴木",
   "高橋",
]

for name in chain(
   first,
   second,
):
   print(name)
 

12.3 isliceで一部を取り出す

islice()は、イテラブルから指定した範囲の要素を取り出すイテレータを返します。通常のスライスを直接使えないイテレータにも利用できます。

無限イテレータから最初の数件だけを取り出す場合にも便利です。

from itertools import count
from itertools import islice

numbers = count(
   start=0,
   step=10,
)

first_five = islice(
   numbers,
   5,
)

for number in first_five:
   print(number)
 

12.4 combinationsとpermutations

combinations()は順序を区別しない組み合わせ、permutations()は順序を区別する並べ方を生成します。

人数の組み合わせ、候補の選択、処理順の全候補などに利用できます。ただし、要素数が増えると生成件数が急激に増加します。

from itertools import combinations

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

for pair in combinations(
   members,
   2,
):
   print(pair)

関数順序を区別重複選択
combinations()しないしない
permutations()するしない
combinations_with_replacement()しないする
product()位置ごとに区別可能

12.5 groupbyとaccumulate

groupby()は連続する同じキーの要素をグループ化し、accumulate()は累積結果を順番に返します。

groupby()は同じキーの要素が連続している必要があるため、必要に応じて事前に並べ替えます。

from itertools import accumulate

sales = [
   1000,
   1500,
   800,
   2000,
]

running_totals = accumulate(sales)

for total in running_totals:
   print(total)
 

13. 実務で使うループ処理

実際のプログラムでは、合計、検索、絞り込み、ファイル処理、階層データ処理などでループを使います。目的に合う組み込み関数があれば、手作業のループより優先できます。

13.1 合計と平均を計算する

数値一覧の合計にはsum()、件数にはlen()を利用できます。ループで一つずつ加算する方法も理解しておく必要がありますが、単純な合計なら組み込み関数のほうが意図が明確です。

空の一覧で平均を計算すると0で割ることになるため、件数を確認します。

scores = [
   85,
   72,
   91,
   68,
]

if scores:
   average = (
       sum(scores)
       / len(scores)
   )
else:
   average = 0

print(average)
 

13.2 条件に一致する値を検索する

最初に一致した値だけが必要な場合は、forbreakまたは関数のreturnを使います。

一致するすべての値が必要な場合は、リスト内包表記などで新しい一覧を作ります。必要な結果件数によって方法を変えてください。

products = [
   {
       "id": "P-001",
       "name": "ノート",
   },
   {
       "id": "P-002",
       "name": "ペン",
   },
]

target_id = "P-002"
found = None

for product in products:
   if product["id"] == target_id:
       found = product
       break

print(found)
 

13.3 データを絞り込む

条件を満たすデータだけを残す場合は、新しいリストへ追加します。元の一覧をループ中に削除しないようにします。

単純な条件なら内包表記、複雑な検証なら通常のループが適しています。

users = [
   {
       "name": "田中",
       "active": True,
   },
   {
       "name": "佐藤",
       "active": False,
   },
   {
       "name": "鈴木",
       "active": True,
   },
]

active_users = []

for user in users:
   if user["active"]:
       active_users.append(user)

print(active_users)
 

13.4 ファイルを一行ずつ読む

テキストファイルオブジェクトはイテラブルなので、for文で一行ずつ読み込めます。ファイル全体を一度に読み込まず、行単位で処理できます。

with文を使うと、処理終了時や例外発生時にファイルを閉じられます。

from pathlib import Path

file_path = Path("data.txt")

with file_path.open(
   "r",
   encoding="utf-8",
) as file:
   for line_number, line in enumerate(
       file,
       start=1,
   ):
       text = line.strip()

       if not text:
           continue

       print(line_number, text)
 

13.5 API形式の階層データを処理する

APIやJSONから取得したデータでは、辞書とリストが入れ子になっていることがあります。外側から構造を確認しながらループします。

外部データはキーや値が欠ける可能性があるため、get()、型確認、検証処理を組み合わせます。

response = {
   "users": [
       {
           "id": 1,
           "name": "田中",
       },
       {
           "id": 2,
           "name": "佐藤",
       },
   ],
}

for user in response.get(
   "users",
   [],
):
   user_id = user.get("id")
   name = user.get(
       "name",
       "名称未登録",
   )

   print(user_id, name)
 

14. よくあるループエラーと対処方法

ループで起こりやすい問題には、無限ループ、終了値の間違い、データ変更、変数の使い回しなどがあります。実行結果だけでなく、各反復で変数がどう変化するか確認してください。

14.1 無限ループが終了しない

whileの条件が常に真であり、breakも実行されなければ無限ループになります。

条件に使う値を更新しているか、終了条件へ到達できるか確認します。意図的な無限ループでも、安全な終了手段を用意してください。

count = 0 while count < 5:    print(count)    count += 1

問題のある例です。

count = 0 while count < 5:    print(count)

後者はcountが変わらないため終了しません。

14.2 終了値が一つずれる

range()の終了値は含まれません。1から10まで処理するためにrange(1, 10)と書くと、9までしか実行されません。

0から始まる位置番号と、1から始まる表示番号も混同しやすい部分です。小さな値で実際に表示して確認してください。

print(
   list(
       range(1, 10)
   )
)

print(
   list(
       range(1, 11)
   )
)

目的正しい例
0から4range(5)
1から5range(1, 6)
1から10range(1, 11)
10から1range(10, 0, -1)

14.3 ループ中のデータ変更

処理中のリストや辞書へ要素を追加、削除すると、要素の取りこぼし、重複処理、実行時エラーが起こる可能性があります。

変更後のデータを別のオブジェクトとして作る方法が安全です。元データを変更する必要がある場合も、削除対象を先に記録します。

numbers = [
   1,
   2,
   3,
   4,
]

remaining = [
   number
   for number in numbers
   if number % 2 != 0
]

print(remaining)
 

14.4 ループ変数を上書きする

外側と内側のループで同じ変数名を使うと、内側の値で上書きされ、意図しない結果になります。

ixだけでなく、rowcolumnuserproductなど、役割が分かる名前を使ってください。

for row in range(3):
   for column in range(3):
       print(
           row,
           column,
       )
 

分かりにくい例です。

for i in range(3):    for i in range(3):        print(i)

14.5 ループをデバッグする

処理が想定と異なる場合は、ループ変数、条件式、途中結果を表示します。何回目の処理で問題が起きたか確認するにはenumerate()が便利です。

本格的な調査では、エディターのデバッガーや組み込みのbreakpoint()を使い、変数の状態を一回ずつ確認します。

numbers = [
   2,
   4,
   0,
   5,
]

for index, number in enumerate(numbers):
   print(
       "処理位置:",
       index,
       "値:",
       number,
   )

   if number == 0:
       breakpoint()

   result = 100 / number
   print(result)
 

15. 読みやすく効率的なループを書く方法

ループは書き方によって、読みやすさと実行効率が大きく変わります。短さだけでなく、終了条件、データの変更、目的の明確さを重視してください。

15.1 組み込み関数を利用する

合計にはsum()、最大値にはmax()、最小値にはmin()、存在確認にはany()、全件確認にはall()など、目的に合う組み込み関数があります。

単純な処理を手作業のループで書くより、何を求めているか読み手へ伝わりやすくなります。

scores = [
   85,
   72,
   91,
]

total = sum(scores)
highest = max(scores)
lowest = min(scores)
all_passed = all(
   score >= 60
   for score in scores
)

print(
   total,
   highest,
   lowest,
   all_passed,
)
 

15.2 不要な処理は早めに飛ばす

対象外のデータを最初にcontinueで除外すると、主要な処理のインデントを浅くできます。

目的の値を見つけた後に追加処理が不要なら、breakreturnで早めに終了します。

users = [
   {
       "name": "田中",
       "active": False,
   },
   {
       "name": "佐藤",
       "active": True,
   },
]

for user in users:
   if not user["active"]:
       continue

   print(
       f"{user['name']}へ"
       "通知します"
   )
 

15.3 同じ計算を繰り返さない

ループ内で毎回同じ結果になる処理は、ループの外側で一度だけ計算します。

ファイル読み込み、設定取得、大きな一覧の並べ替えなどを毎回実行すると、処理時間が増えます。

tax_rate = 0.1

products = [
   1000,
   2000,
   3000,
]

multiplier = 1 + tax_rate

for price in products:
   total = int(
       price * multiplier
   )
   print(total)
 

15.4 処理を関数へ分ける

ループ本体が長くなったら、一件分の処理を関数へ分けます。ループは「何を繰り返すか」を示し、詳細な処理は関数内へ移します。

関数単位でテストできるため、エラーの原因も特定しやすくなります。

def calculate_total(
   product: dict,
) -> int:
   return (
       product["price"]
       * product["quantity"]
   )


products = [
   {
       "price": 1000,
       "quantity": 2,
   },
   {
       "price": 500,
       "quantity": 3,
   },
]

for product in products:
   total = calculate_total(product)
   print(total)
 

15.5 ループを書くときの確認項目

ループを書いた後は、開始条件、終了条件、空データ、境界値、データ変更などを確認します。

特にwhileでは終了条件、入れ子ループでは実行回数、外部データでは欠損値を重点的に確認してください。

確認項目確認内容
対象何を一件ずつ処理するか
初期値開始時点の変数は正しいか
終了条件必ず終了できるか
境界値最初と最後の要素を含むか
空データ要素が0件でも動くか
データ変更反復対象を直接変更していないか
中断breakの位置は正しいか
スキップcontinueで必要処理を飛ばしていないか
入れ子実行回数が増えすぎないか
可読性変数名と処理目的が明確か

日常的には、次の方針を意識します。

  1. 一覧の処理にはforを優先する
  2. 条件が成立する間の処理にはwhileを使う
  3. 回数指定にはrange()を使う
  4. 位置番号にはenumerate()を使う
  5. 対応する複数データにはzip()を使う
  6. 長さが一致すべき場合はstrict=Trueを検討する
  7. 終了にはbreak、現在の回の除外にはcontinueを使う
  8. 反復対象のデータをループ中に変更しない
  9. 複雑な処理は関数へ分割する
  10. 組み込み関数で表現できないか確認する

おわりに

Pythonのループ処理では、主にfor文とwhile文を使います。リスト、文字列、辞書などの要素を順番に処理する場合はfor文、指定した条件が成立している間だけ処理を続ける場合はwhile文が適しています。

回数を指定する場合はrange()、位置番号も必要ならenumerate()、複数の一覧を同時に処理するならzip()を利用できます。処理を途中で終了するbreak、現在の回だけを飛ばすcontinue、何も実行しないpassの違いも正しく理解する必要があります。

ループを書くときは、正常なデータだけでなく、空の一覧、最初と最後の値、不正な入力、終了条件も確認してください。特にwhile文は、条件に使う値が更新されなければ無限ループになるため、初期値、条件式、更新処理を一組として確認します。

最初は、数値の合計、偶数の抽出、名前の一覧表示など、小さなコードから練習してください。その後、辞書のリスト、ファイル処理、API形式の階層データへ進むことで、実際のPythonプログラムでループを活用できるようになります。

LINE Chat