第6回:繰り返し処理(講義)#

担当:相澤宏旭

内容#

第6回 繰り返し処理では,ある処理を条件式に基づき繰り返す処理について学ぶ.プログラムにおいて繰り返し処理はリストなど複数のデータ値を持つ変数に対する演算処理,ユーザ入力の待機時間の実装など様々な場面で利用される.さらに,第4回で学んだ条件分岐と組み合わせることで,指定した条件が満たされたとき繰り返し処理を停止させたりするなど,処理をより複雑に制御できるようになる.このような処理を実現するために,Pythonでは for文やwhile文がサポートされている.具体的に,これらの文法について以下を学ぶ.

  • 一定回数繰り返すまたは条件に応じて処理を反復するための,for文とwhile文の基本文法を理解する.

  • 繰り返し処理を強制的に制御するbreakcontinueの文法を学び,その動作を確認する.

  • Pythonに実装されているより実用的な繰り返し処理を学ぶ.

繰り返し処理とは?#

繰り返し処理とは,言葉の通り,ある処理を複数回繰り返す処理である.前述したように,繰り返し処理によって,特定の処理を複数回反復することができ,また条件分岐と繰り返し処理を組み合わせることで,より複雑なプログラムを実装することができる.何より,繰り返し処理によって,複数回同じプログラムを記述する必要がなくなるため,コードの可読性を改善し,コード量も減らすことができる.

では,以降で繰り返し処理の問題場面を確認しよう.

繰り返し処理の利用場面#

第2回の講義で学んだリストの各要素に対して何らかの処理を加えてprintで出力する場面を考える.例えば,以下のような曜日の文字列が格納されたリストを与えられ,今日は...ですと要素を出力することを考える.このとき繰り返し処理を利用しなければ,以下のようなインデックスによる出力方法が考えられる.

days = ['月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日', '日曜日']
print(f'今日は{days[0]}です')
print(f'今日は{days[1]}です')
print(f'今日は{days[2]}です')
print(f'今日は{days[3]}です')
print(f'今日は{days[4]}です')
print(f'今日は{days[5]}です')
print(f'今日は{days[6]}です')
今日は月曜日です
今日は火曜日です
今日は水曜日です
今日は木曜日です
今日は金曜日です
今日は土曜日です
今日は日曜日です

このように愚直にprint文を要素数だけ記述すると各要素に対して何らかの演算と出力できる.

続いて,平日の場合は...は平日です,休日の場合は...は休日ですと出力することを考える.同様にprint文を使って以下のように書ける.

print(f'{days[0]}は平日です')
print(f'{days[1]}は平日です')
print(f'{days[2]}は平日です')
print(f'{days[3]}は平日です')
print(f'{days[4]}は平日です')
print(f'{days[5]}は休日です')
print(f'{days[6]}は休日です')
月曜日は平日です
火曜日は平日です
水曜日は平日です
木曜日は平日です
金曜日は平日です
土曜日は休日です
日曜日は休日です

しかしながら,このようなprint文による処理は問題がある.わかりやすい問題点をいくつか述べると,

  1. 要素数が増えるとprint文の行数が増えること

  2. 要素の順番が可変の場合はprint文を修正する必要があること

  3. 要素の数が可変の場合はprint文では対処しきれないこと

が挙げられる.このような問題をスマートに解決する方法が繰り返し処理である.

for#

前述した例のように,リストの各要素に対して順番に処理を適用したいとき,そのような場面で利用されるのが for文である.一般的な for文の構文は以下である.

for 要素を保存する仮変数 in リスト:
    繰り返される処理

ここで,for文では繰り返される処理をインデントによるコードブロックで記述している.インデントの詳細については第4回 条件分岐を参照されたい.

もちろん,リストだけでなく,辞書やタプルといったデータ型も繰り返すことができる.

では,使用例として曜日を要素として格納したdaysリストの各要素を出力する.

for day in days:
    print(day)
月曜日
火曜日
水曜日
木曜日
金曜日
土曜日
日曜日

出力結果からもわかるように,リストの各要素に対してprint文を書くことなく,すべての要素を出力できている.

続いて,各要素に処理を加えて出力する場合を確認する.前述のように,今日は...ですと文字列を加える処理をfor文で記述する.

for day in days:
    print(f'今日は{day}です')
今日は月曜日です
今日は火曜日です
今日は水曜日です
今日は木曜日です
今日は金曜日です
今日は土曜日です
今日は日曜日です

このようにコードの行数を削減できるだけでなく,リストの要素数が可変であっても処理できることが繰り返し処理の利点である.

最後に,平日の場合は...は平日です,休日の場合は...は休日ですと出力するプログラムを条件分岐と組み合わせて作成する.

for day in days:
    if day in ['月曜日', '火曜日', '水曜日', '木曜日', '金曜日']:
        print(f'{day}は平日です')
    elif day in ['土曜日', '日曜日']:
        print(f'{day}は休日です')
月曜日は平日です
火曜日は平日です
水曜日は平日です
木曜日は平日です
金曜日は平日です
土曜日は休日です
日曜日は休日です

このプログラムでは days から各要素を順番に day に取り出し,day の中身が平日の要素 ['月曜日', '火曜日', '水曜日', '木曜日', '金曜日'] か休日の要素 ['土曜日', '日曜日'] かを in 演算子で条件分岐を行い,その結果に応じた文字列を出力している.

このように,繰り返し処理は条件分岐と組み合わせることで非常に柔軟な処理を実現できる.

Caution

利用場面として紹介した特定の処理を予め予想し固定の処理としてコーディングすることはハードコーディングと呼ばれバグの温床となる.今回の場合では悪い例として要素数を固定してprint文で出力したが,for文を使って要素数や順番が可変であっても対応できるコーディングをすることが望まれる.

Tip

発展的な内容であるがfor文などの繰り返し処理に利用できるオブジェクトはイテラブルオブジェクト(itarable object)と呼ばれる.イテラブルオブジェクトとして代表的なデータ型はリスト,タプル,辞書,文字列である.これらのオブジェクトはfor文で上記のように利用できる.一方で,数値データや論理値はイテラブルでないオブジェクトである.

range関数#

前述のfor文の例では,繰り返し処理のために曜日や集合の要素が格納されたリストを予め作成する必要があった.

しかしながら,例えば,\(1000\)回繰り返し処理をしたいとき,\(1000\)個の値が格納されたリスト

num_list = [0, 1, ..., 999]

を定義しなければならない.説明のため,...としたが,実際は,前述までの例のように愚直にリストに値を格納する必要があり,非常に手間がかかる.

そこで,このような一定回数だけ処理を繰り返したい場面のために,Pythonではrange関数がサポートされている.このrange関数は任意の回数反復可能な整数列を作成できる.

range関数は以下のように利用できる.

for 仮変数 in range(start, end, step):
    繰り返される処理

ここで,startは開始時の数,endは反復回数,stepは一定回数ごとにスキップする数である.

Tip

range文ではデフォルト引数としてstart=0step=1が指定されているため,startstepは省略できる.そのため,range(0,n,1)range(n)と書くことができる.range関数はforwhileと同様に組み込み関数と呼ばれる.デフォルト引数や関数は第8回 関数とスコープで紹介するので,print文やif文のようにまずは使い方だけを習得してほしい.

以下,range関数のサンプルコードである.例えば,range(0,10,1)は0から9までの整数列を返す処理となる. range関数のstartendstepを変更し,挙動を確認されたい.

for i in range(0,10,1):
    print(i)
0
1
2
3
4
5
6
7
8
9
for i in range(10):
    print(i)
0
1
2
3
4
5
6
7
8
9
for i in range(5,10):
    print(i)
5
6
7
8
9
for i in range(0,10,2):
    print(i)
0
2
4
6
8

よく利用されるテクニックとしてrange関数によるリストのインデキシングがある.例えば,len関数でリストの要素数を取得できるので,以下のようにrange関数を定義すると,インデックスによるリストの走査ができる.

for i in range(len(days)):
    print(days[i])
月曜日
火曜日
水曜日
木曜日
金曜日
土曜日
日曜日

前述の例であるがリストは反復可能なデータ型なので以下のコードのようにも書ける.上記のコードは以下と同じである.

for i in days:
    print(i)
月曜日
火曜日
水曜日
木曜日
金曜日
土曜日
日曜日

for文のまとめ

  • for文はリストなどの複数要素を繰り返すときに便利である.

  • for文の処理はインデントを使って記述する必要がある.

  • range関数を利用することで一定回数だけ繰り返す処理を実装できる.

while#

Pythonではfor文以外にもいくつかの繰り返し処理が実装されている.その中でも代表的な繰り返し処理が条件式がTrueである間,処理を繰り返すwhile文である.条件式については第4回 条件分岐を参照されたい.

while文の構文は以下である.

while 条件式:
    条件式がTrueである間, 繰り返される処理

for文との違いは,for文はあるオブジェクトの要素数分またはrange関数による一定回数の繰り返し処理に対して,while文はある条件がTrueの間処理を繰り返す.ただし,利用場面の違いはあるがfor文であっても条件式に基づいた繰り返し処理を実装でき,while文についても一定回数後に繰り返しを終了するよう実装できる.このような書き換えは,後述するbreak文や条件分岐処理を使って実現できる.

while文の挙動を実例から確認しよう.

以下のwhile文は変数count\(0\)以上のときTrueとなる条件式count >= 0に基づいて繰り返される.条件式がTrueの間はcountの値の出力と繰り返されるたびにcountの変数を\(-1\)する処理が実行される.countの値が繰り返すたびに減少するため,処理がcount\(+1\)回分行われるとcountの中身は負の値となり繰り返し処理が停止する.

count = 10
while count >= 0:
    print(count)
    count -= 1
10
9
8
7
6
5
4
3
2
1
0

Important

上記のコードにおいてcountを処理のたびに\(-1\)しない処理を考える.このとき,countの値が減少しないため,while文は強制的にプログラムを停止しない限り繰り返し処理が続く.このような意図せず実行された,終わらない繰り返しは無限ループと呼ばれる.プログラミングのテクニックとして,無限ループはユーザ入力の待機待ちの実装等で利用されることもあるが,繰り返し(ループ)が終了する条件もプログラムされている.Google ColabではCtrl+CInterruptボタンを押さない限り動作し続けるため注意されたい.

break文による繰り返しの終了#

for/while文で,繰り返し(ループ)を中断したいとき,break文を使用する.これによって,前述の無限ループのように「Ctrl+C」や「Interrupt」ボタンを押すことなく,無限ループを強制的に終了させることができる.

break文は,例えば,何回繰り返すか決まっていない繰り返しで,ある条件式が満たされたら繰り返しを終了させたい場面で有効である.一般的に,以下のように利用される.

while 繰り返しの条件式:
    繰り返しの条件式がTrueの間, 繰り返される処理
    if 繰り返しを終了させる条件式:
        break

以下のwhile文による繰り返しに与えられる条件式はTrueの条件式であり,中断処理をしない限り繰り返しが続く.ここでは break文の使用例として,繰り返しの外で変数 i を定義し,繰り返すたびに繰り返し内の処理として条件式の判定を行う.内部の条件式は i\(0\) 未満となったとき繰り返しを強制的に抜ける break文を実行し,i\(0\) 以上のとき i\(-1\) する処理である.

i = 10
while True:
    if i < 0:
        break
    else:
        print(i)
        i -= 1
10
9
8
7
6
5
4
3
2
1
0

このように繰り返し内部に条件式とbreak文を設けることで無限繰り返しによる処理を実装できる.

Important

break文はwhile文内だけでなくfor文でも利用できる.

continue文による次の繰り返しの開始#

continue文は,繰り返しを終了することなく,その後の処理をスキップして次の繰り返しに進むことができる.条件式を満たすとき,処理1を実行するが,処理2は実行しない繰り返し文を実行したいとき以下のようにcontinueを利用する.

while 繰り返しの条件式:
    処理1
    if 処理2をスキップさせる条件式:
        continue
    処理2

continue文の利用場面として,例えば,整数のリストを与えられ,偶数の要素だけ表示するプログラムを考える.以下がそのコードとなるので確認されたい.

nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

i = 0
while i < len(nums):
    if nums[i] % 2 != 0:
        i += 1
        continue
    print(nums[i])
    i += 1
2
4
6
8
10

上記のコードのwhile文は繰り返し回数iが整数リストnumの要素数len(nums)より小さいときTrueとなる条件式

i < len(nums)

に基づいて繰り返される.つまりはリストの要素数だけ繰り返す処理となっている.

そして,繰り返し内ではi番目のリストの要素nums[i]について\(2\)で割り切れるかを判定する.これは以下のコードである

if nums[i] % 2 != 0:

ここで,\(2\)で割り切れないとき,つまり,奇数であるとき,i\(+1\)してcontinueする.このとき,continue後の処理printが実行されていないことを確認されたい.

一方で,\(2\)で割り切れるとき,つまり,偶数であるとき,print文でi番目のリストの要素nums[i]を出力し,i\(+1\)して次の繰り返し処理に進む.

Important

continue文はwhile文内だけでなくfor文でも利用できる.

while文のまとめ

  • while文は条件式がTrueのとき処理を繰り返す.

  • break文やcontinue文を組み合わせると複雑な繰り返し制御を実装できる.

  • 常にTrue状態となる無限ループとなる場合があるので注意する.

for文によるリストの作成#

繰り返し処理はリストの作成にも利用できる.

具体的に,空のリストにappend関数を使ってfor文とrange関数による繰り返し処理から得られる数値を追加することでリストを作成できる.

以下に\(0\)から\(9\)までの計10個の要素を持つリストをfor文を利用するコードを示す.

l = []
for i in range(10):
    l.append(i)
print(l)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

上記のコードでは,要素が空のリスト l=[] を作成し,繰り返し処理で得られる \(i=0,1,...,9\) の値をリストにappendしている.リストを出力した結果が [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] となっていることを確認されたい.

また,特定の条件を満たすリストや数値以外のリストを作成する際に便利である.例えば,\(3\)で割り切れない数値のみを持つリストを作成してみよう.これは前述のコードに条件分岐を組み合わせることで実現できる.

以下にfor文による\(3\)で割り切れない数値のみを持つリストの作成コードを示す.

l = []
for i in range(10):
    if i % 3 == 0:
        continue
    l.append(i)
print(l)
[1, 2, 4, 5, 7, 8]

上記のコードは,\(3\) で割り切れないときのみ値を append し,割り切れる場合は continue 文を用いて以降の処理をスキップして次の繰り返しに移行する.結果を出力すると,\(3\)の倍数を除いたリスト [1, 2, 4, 5, 7, 8] が作成されていることがわかる.


【発展】多重ループ#

for文の応用として,複数のオブジェクトの要素を繰り返す多重ループがある.多重ループはネストループとも呼ばれ,2重だけでなく3重以上のループも実装できる.このような多重ループは,例えば,2つの要素集合のリスト間の組み合わせを出力する場面で利用できる.

以下は集合\(A=\{a,b,c\}\)と集合\(B=\{x,y,z\}\)間の組み合わせ出力する多重ループのサンプルコードである.各集合はリストとして実装し,2重ループとして,各組み合わせをprint文で出力する.

A = ['a', 'b', 'c']
B = ['x', 'y', 'z']

for i in A:
    for j in B:
        print(i, j)
a x
a y
a z
b x
b y
b z
c x
c y
c z

Tip

多重ループは後述のwhile文,break文,continue文と組み合わせると便利である.しかしながら,多重ループはコードの可読性を下げるため多用は厳禁である.Pythonには itertools を代表とするループ処理のライブラリも提供されており,複雑なループを実装したいときに便利である.

【発展】イテレータ#

厳密にはPythonにおいてrange関数は イテレータ と呼ばれる仕組みである.イテレータは次の要素を順番に取り出し,読み出し可能な要素がなくなったときに終了変数を返すような機能を持つ.

range関数をprintしてみるとリストのように中身の要素が出力されないことに注意されたい.

print(range(3))
range(0, 3)
print(type(range(3)))
<class 'range'>

range関数から直接にリストやタプルが作成できると便利である.データ型の変換を利用するとrange関数からリストやタプルが作成できる.

x = list(range(3))
print(x)
[0, 1, 2]
x = tuple(range(3))
print(x)
(0, 1, 2)

リストをイテレータ化して,順番に取り出すこともできる.具体的に,

  1. iter() にリストを渡しイテレータ化

  2. 返ってきたイテレータに対してnext()を使って呼び出し

  3. 終了変数 StopIteration が発生するまで繰り返し となる

以下のコードで確認されたい.

l = [3, 2, 1, 0]
l = iter(l)
print(next(l))
print(next(l))
print(next(l))
print(next(l))
# print(next(l)) # StopIterationのエラーが発生する
3
2
1
0

【発展】enumerate関数#

enumerate関数はfor文やwhile文と同様に繰り返し処理のためのPythonの組み込み関数である.enumerate関数はループの回数(=要素の数)をカウントする利用場面で便利である.その具体例を以下のセルに示す.

i = 0
for v in ['a', 'b', 'c']:
    print(i, v)
    i += 1
0 a
1 b
2 c

ループと共にその回数をカウントしたいとき,上のコードのようにカウント用の変数iを定義して,ループ処理のたびにiのカウント数を\(+1\)する必要がある.このようなカウントは以下のようにrange関数でも実現できるが,多重ループでのカウントなどでコードの可読性が下がる可能性がある.

l = ['a', 'b', 'c']
for i in range(len(l)):
    print(i, l[i])
0 a
1 b
2 c

enumerate関数は以下のように定義することで要素のカウントとループを同時に行うことができる.

for カウント, 要素 in enumerate(リスト):
    処理

for文でリストやrange関数をenumerate関数に置き換えるだけで実装でき,多重ループであっても可読性は高い.以下のセルに前述のfor文でのループ数のカウントをenumerate関数で置き換えたコードを示す.

for i, v in enumerate(['a', 'b', 'c']):
    print(i, v)
0 a
1 b
2 c

【発展】zip関数#

zip関数は複数の反復可能なオブジェクトの繰り返し処理をしたいときに便利である.多重ループでの例のように,集合 \(A=\{a,b,c\}\) と集合 \(B=\{x,y,z\}\) について各要素を順番に取り出す処理を考える.望まれる出力は以下である.

a x
b y
c z

このような出力をfor文で実現したいとき要素のインデックスを要素数分だけ繰り返すrange関数を作成すれば良い.以下が参考例である.

A = ['a', 'b', 'c']
B = ['x', 'y', 'z']
for i in range(len(A)):
    print(A[i], B[i])
a x
b y
c z

enumerate関数の利用例とは異なり,既に可読性の高い繰り返し処理がfor文で実現できている.シンプルな処理であれば十分であるが,要素数が異なる複数のオブジェクトを反復させたい場合や機械学習など大規模なプログラムを作成時に非効率的になる可能性がある.実際に,要素数が異なる複数オブジェクトを上記のようにfor文で繰り返し処理をしようとすると,少ない要素数のオブジェクトで参照エラーが発生する.

# A = ['a', 'b', 'c', 'd']
# B = ['x', 'y', 'z']
# for i in range(len(A)):
#     print(A[i], B[i])

上記の例では集合\(A\)\(B\)の要素数を予め取得し,少ない要素数を持つ集合に対してrange関数を作成すれば良いが,Pythonにはこのような処理をすることなく複数オブジェクトを同時に反復する効率的な組み込み関数が提供されている.リストの場合,zip関数は以下のように定義できる.

for リストAの要素, リストBの要素, ... in zip(リストA, リストB, ...):
    処理

zip関数ではオブジェクトの要素数が異なる場合はもっとも少ない要素数を持つオブジェクトを基準に繰り返し処理がなされる.以降のセルに,等しい要素数を持つ反復と異なる要素数を持つ反復の例を示す.

for u, v in zip(['a', 'b', 'c'], ['x', 'y', 'z']):
    print(u, v)
a x
b y
c z
for u, v in zip(['a', 'b', 'c', 'd'], ['x', 'y', 'z']):
    print(u, v)
a x
b y
c z

【発展】辞書とfor#

辞書を用いた繰り返し処理を紹介する.これまではリスト型を中心に繰り返し処理を説明したが,ここでは,辞書を用いた繰り返し処理を紹介する.第2回の講義で説明したように,辞書は変数keyに基づいて値valueを参照するデータ型である.辞書は反復可能なイテラブルなデータ型であるため,len関数がサポートされているが,リストのように要素の順番を示すインデックス変数iを利用した参照ができない.このような辞書型であってもシンプルかつ効果的に繰り返し処理をするための関数が提供されている.

以降のセルでは第2回の講義で紹介した辞書の値のみ,キーワードのみ,またはその両方を取得する関数を復習し,for文による繰り返し処理を紹介する.

d = {'key1': 1, 'key2': 2, 'key3': 3}
print(d)
{'key1': 1, 'key2': 2, 'key3': 3}

辞書の要素にアクセスするためのキーワードは辞書.keys()で取得できる.以下のように記述することで辞書のキーワードを繰り返すことができる.

for key in d.keys():
    print(key, d[key])
key1 1
key2 2
key3 3

辞書のキーワードによって参照される値は辞書.values()で取得できる.以下のように記述することで辞書の値を繰り返すことができる.

for value in d.values():
    print(value)
1
2
3

辞書.items()で辞書のキーワードと値を同時に取得できる.同様にfor文と組み合わせることで辞書のキーワードと値を同時に繰り返すことができる.

for key, value in d.items():
    print(key, value)
key1 1
key2 2
key3 3

【発展】内包表記#

前述したfor文によるリストの作成は便利であるが,コードはやや煩雑となる.Pythonでは内包表記と呼ばれる文法が定義されており,非常にシンプルかつ高速にリストの作成ができる.以下に,\(0\)から\(9\)までの計10個の要素を持つリストを内包表記で作成するコードを示す.

l = [i for i in range(10)]
print(l)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

出力結果とコードからわかるように,3行の処理が1行でリストを作成できる.続いて,for文による\(3\)で割り切れない数値のみを持つリストの作成コードを示す.ここでは条件分岐を行うために,以下のように3項演算子と同様の表記の条件式を繰り返し処理の後ろに記述する.

l = [i for i in range(10) if i % 3 != 0]
print(l)
[1, 2, 4, 5, 7, 8]

上記の内包表記は条件式i % 3 != 0Trueのときに\(i\)を追加する処理となっている.内包表記は多重ループに対しても利用できるが可読性が著しく下がるため推奨されない.また,内包表記はリストだけでなく辞書も作成できる.以下に,zip文を応用した辞書の内包表記の例を示す.

d = {k: v for k, v in zip(['key1', 'key2', 'key3'], [1, 2, 3])}
print(d)
{'key1': 1, 'key2': 2, 'key3': 3}