+ 作るログイン
図を読み込んでいます…
1 / 22
JS から来た人の Python ― どこから来て、なぜ動くのか / 全19章

Python 06:繰り返し・分岐・例外

発行
アニメモ公式
版
第1版
改訂
コマ
全22

Python の for は1種類だけ。その裏で iter() と next() が「取り出し係」を動かし、StopIteration で終わる流れを図で追います。range・enumerate・zip・items、while と for の else、switch に似て非なる match、そして例外が呼び出しを遡って except に受け止められる様子、try/except/else/finally の4つの枠、JS の try/catch との違い、後片付けを保証する with の仕組み(__enter__ と __exit__)までを、天気コマンド tenki の例で説明します。

3都市ぶん順に表示したい。JS なら for は4通りある

第5章で、tenki の「1都市の天気を表示する」処理を関数にまとめました。次にやりたいのは、東京・大阪・札幌の3都市ぶんを順に表示することです。つまり繰り返しです。

JS で繰り返しを書こうとすると、書き方がいくつもあります。

JS の書き方何を回すか
for (let i = 0; i < a.length; i++)数を数えて、添字で取り出す
for (const c of a)配列などの値を順に
for (const k in obj)オブジェクトのキーを順に
a.forEach((c) => …)配列のメソッドに関数を渡す

どれを使うか迷ったことがある人も多いでしょう。for…in を配列に使って添字が文字列で出てきて驚いた、という経験があるかもしれません。

Python の for は1種類だけです。図の右のように for city in cities: と書きます。では、これは JS のどれに当たるのでしょうか。番号も欲しいときはどうするのでしょうか。

もう1つ気になることがあります。3都市のうち、たとえば「名古屋」のデータが見つからなかったら、そこでプログラム全体が止まってしまうのか。JS の try/catch のようなものはあるのか。ファイルを開いたら閉じ忘れないようにするには、どうするのか。

この章では、処理の流れを変える道具(分岐・繰り返し・例外)をまとめて見ていきます。とくに for が裏で何をしているかと、例外がどこを通って受け止められるかを、図で1段ずつ追いかけます。

※2026年9月時点(Python 3.13)の情報です。

  • JS から来た人の Python 06 ― 繰り返し・分岐・例外
  • 1 / 22
  • 3都市ぶん順に表示したい。JS なら for は4通りある
  • // JS:繰り返しの書き方が4通り
  • # Python:これだけ
  • for (let i = 0; i < cities.length; i++) {…}
  • for city in cities:
  • for (const c of cities) {…}
  • print(city)
  • for (const k in obj) {…}
  • cities.forEach((c) => {…})
  • 注意
  • 名古屋のデータが無かったら?
  • 1都市の失敗で、全部止まってほしくない
  • 疑問1:Python の for は、JS のどれに当たる? 番号が欲しいときは?
  • 疑問2:失敗はどう受け止める? try/catch はある? ファイルの閉じ忘れは?
  • ひらめき
  • Python の for は1種類だけ。途中の1都市が失敗しても止めない方法も知りたい。

Python の for は「取り出せるもの」から1つずつ

Python の for を一言でいうと、「中身を1つずつ取り出せるもの」から、尽きるまで1つずつ受け取って、中身の処理を繰り返す仕組みです。

たとえるなら、袋から1つずつ物を取り出す係です。袋がリストでも、文字列でも、ファイルでも、係に「次をちょうだい」と頼む手順は同じです。袋ごとの取り出し方の違いは、袋の側が知っています。

この「1つずつ取り出せるもの」を Python ではイテラブル(iterable=反復できるもの)と呼びます。リスト・タプル・文字列・辞書・集合・ファイル・range(…) などが、すべてイテラブルです。

JS の書き方と並べると、図の表のようになります。

やりたいことJSPython
要素を順にfor (const c of cities)for c in cities:
番号も欲しいfor (let i = 0; …) や forEach((c, i) => …)for i, c in enumerate(cities):
オブジェクトのキーfor (const k in obj)for k in weather:

Python の for … in は、JS の `for…of` に近いものです。名前は in ですが、JS の for…in(キーを回す)とは別物なので注意してください。

実は JS の for…of も、裏では「イテレータ」という同じ考え方で動いています(Symbol.iterator)。Python はそれを唯一の for にした、と考えるとわかりやすいでしょう。

この裏側の仕組みは、少し先で図を動かして見ます。その前に、分岐の if を片付けておきます。

  • JS から来た人の Python 06 ― 繰り返し・分岐・例外
  • 2 / 22
  • Python の for は「取り出せるもの」から1つずつ
  • 荷物
  • 反復できるもの
  • 尽きるまで
  • iterable(イテラブル)
  • 1つずつ渡す
  • コード
  • for の中身
  • 受け取って処理する
  • 繰り返す
  • やりたいこと
  • JS
  • Python
  • 要素を順に
  • for…of / forEach
  • for c in cities:
  • 番号も欲しい
  • for (let i = 0; …)
  • for i, c in enumerate(cities):
  • オブジェクトのキー
  • for…in
  • for k in weather:
  • ひらめき
  • リストでも文字列でも辞書でもファイルでも、for は同じ書き方。中身の取り出し方は相手が知っている。

if / elif / else:波かっこの代わりにコロンと字下げ

条件で処理を分ける if は、JS とほとんど同じ考え方です。違うのは書き方です。

図の左が JS、右が Python です。気温 temp から「暑い」「ちょうどいい」「寒い」を決めています。

違いJSPython
条件のかっこif (temp >= 30) と必須if temp >= 30: と不要
ブロックの始まり{行末の :
ブロックの範囲{ } の中字下げされている行(第3章)
「そうでなくて、もし」else ifelif
三項演算子条件 ? A : BA if 条件 else B

Python の三項演算子(条件式)は、並び順が JS と違います。

python
label = "暑い" if temp >= 30 else "ふつう"

「"暑い"、もし temp が30以上なら。そうでなければ "ふつう"」と、英語の語順で読むとわかりやすくなります。

条件に書ける値の真偽(空の文字列や 0、空のリスト、None は偽になる)や、and・or・not は第3章で見たとおりです。JS の &&・||・! は Python では使えません。

python
if city and temp is not None:
    print(city, temp)

is not None は「None ではない」の決まった書き方です(第3章の is)。

  • JS から来た人の Python 06 ― 繰り返し・分岐・例外
  • 3 / 22
  • if / elif / else:波かっこの代わりにコロンと字下げ
  • JS
  • Python
  • if (temp >= 30) {
  • if temp >= 30:
  • label = "暑い";
  • label = "暑い"
  • } else if (temp >= 20) {
  • elif temp >= 20:
  • label = "ちょうどいい";
  • label = "ちょうどいい"
  • } else {
  • else:
  • label = "寒い";
  • label = "寒い"
  • }
  • label = temp >= 30 ? "暑い" : "ふつう";
  • label = "暑い" if temp >= 30 else "ふつう"
  • Python では、字下げそのものがブロック(第3章)。閉じかっこの代わりに、字下げを戻したところでブロックが終わる
  • ひらめき
  • else if は elif と書く。条件のかっこは要らない。三項演算子は「A if 条件 else B」。

for の裏側 ①:iter() で「取り出し係」を作る

ここからは、for city in cities: print(city) と書いたとき、Python が裏で何をしているかを1段ずつ見ます。

図の左下のコードが、その「中身」を書き下したものです。for はこれと同じことを自動でやっています。実際にこのコードを実行しても、for と同じ結果になります。

最初にやるのは、iter(cities) を呼ぶことです。`iter()` は組み込みの関数で、渡されたものに「あなたの中身を1つずつ出してくれる係をちょうだい」と頼みます。返ってくるものをイテレータ(iterator)と呼びます。

実際に REPL(第1章)で試すと、こうなります。

python
>>> cities = ["東京", "大阪", "札幌"]
>>> it = iter(cities)
>>> it
<list_iterator object at 0x7f73be0adba0>

list_iterator という、リスト専用の取り出し係ができました(0x… の数字は実行するたびに変わります)。

なぜリストが直接「次」を出さず、わざわざ係を作るのでしょうか。リスト自体は「どこまで出したか」を覚えていないからです。覚えているのは係のほうです。

このおかげで、同じリストを2つの for で同時に回しても(たとえば for の中にもう1つ for を入れても)、それぞれが自分の係を持つので、位置が混ざりません。

  • JS から来た人の Python 06 ― 繰り返し・分岐・例外
  • 4 / 22
  • for の裏側 ①:iter() で「取り出し係」を作る
  • cities(リスト)
  • 更新
  • for の中身
  • city = ?
  • 東京
  • 大阪
  • 札幌
  • iter()
  • 取り出し係
  • next()
  • イテレータ
  • 次は 0 番目
  • # for city in cities: print(city) の中身
  • $ python3 tenki.py
  • it = iter(cities)
  • while True:
  • try:
  • city = next(it)
  • except StopIteration:
  • break
  • リストそのものは「どこまで出したか」を覚えない。
  • print(city)
  • 覚えているのは取り出し係のほう。
  • ひらめき
  • for はまず相手に iter() を頼み、中身を1つずつ出してくれる係(イテレータ)を受け取る。

for の裏側 ②:next() で1つ取り出し、中身を実行する

係ができたら、次は `next(it)` で1つ取り出します。係は「東京」を返し、自分の位置を1つ進めます。

python
>>> next(it)
'東京'
>>> next(it)
'大阪'

for は、取り出した値に city という名前を付けて(第4章の「札を付ける」)、for の中身、つまり print(city) を1回実行します。図の右下の出力に「東京」が出ました。

ここで大事なのは、`city` は毎回、新しい値に付け替えられる名前にすぎないということです。JS の for (const c of …) では回ごとに新しい c が作られますが、Python ではループが終わった後も city という名前は残り、最後に取り出した値を指したままになります。

python
>>> for city in cities:
...     pass
...
>>> city
'札幌'

Python には JS のような「ブロックの中だけの変数」(let のスコープ)がありません。for や if の中で作った名前は、その関数(またはファイル)全体で見えます(第5章のスコープ)。

  • JS から来た人の Python 06 ― 繰り返し・分岐・例外
  • 5 / 22
  • for の裏側 ②:next() で1つ取り出し、中身を実行する
  • cities(リスト)
  • 更新
  • for の中身
  • city = "東京"
  • 東京
  • 大阪
  • 札幌
  • iter()
  • 取り出し係
  • next()
  • イテレータ
  • 東京
  • 次は 1 番目
  • # for city in cities: print(city) の中身
  • $ python3 tenki.py
  • it = iter(cities)
  • while True:
  • 東京
  • try:
  • city = next(it)
  • except StopIteration:
  • break
  • 取り出し係は「次は1番目」と位置を進める。
  • print(city)
  • city は毎回、新しい値に付け替わる札(第4章)。
  • ひらめき
  • next() が「東京」を返す。それを city という名前に付けて、for の中身を1回実行する。

for の裏側 ③:尽きるまで同じことを繰り返す

あとは同じことの繰り返しです。

回next(it) が返すものcity出力
1回目"東京""東京"東京
2回目"大阪""大阪"大阪
3回目"札幌""札幌"札幌

図では、リストの3つの札がすべて灰色になり、取り出し係の位置は「次は3番目」になりました。リストには0〜2番目しか無いので、もう出すものはありません。

私たちが書いたのは print(city) だけです。iter() を呼ぶのも、next() を何度も呼ぶのも、名前を付け替えるのも、すべて for が代わりにやってくれています。

逆にいえば、`iter()` と `next()` に応えられる物なら、何でも `for` で回せます。リストかどうかは関係ありません。自分で作ったクラスでも、__iter__ と __next__ という特殊メソッド(第7章)を書けば回せるようになります。

  • JS から来た人の Python 06 ― 繰り返し・分岐・例外
  • 6 / 22
  • for の裏側 ③:尽きるまで同じことを繰り返す
  • cities(リスト)
  • 更新
  • for の中身
  • city = "札幌"
  • 東京
  • 大阪
  • 札幌
  • iter()
  • 取り出し係
  • next()
  • イテレータ
  • 札幌
  • 次は 3 番目
  • # for city in cities: print(city) の中身
  • $ python3 tenki.py
  • it = iter(cities)
  • while True:
  • 東京
  • try:
  • 大阪
  • city = next(it)
  • 札幌
  • except StopIteration:
  • break
  • print(city)
  • for の中で書いたのは print(city) だけ。
  • iter と next は for が代わりに呼んでいる。
  • ひらめき
  • next() → 中身 → next() → 中身…。大阪、札幌と進み、3つとも出し終わった。

for の裏側 ④:尽きたら StopIteration で終わる

4回目の next(it) では、もう出すものがありません。このとき係は値を返す代わりに、`StopIteration` という例外を投げます。

python
>>> next(it)
Traceback (most recent call last):
  ...
StopIteration

例外とは「ふつうに値を返せない事情が起きた」ことを知らせる仕組みで、この章の後半で詳しく見ます。ここでは StopIteration はエラーではなく「おしまい」の合図だと考えてください。

図の左下のコードでは、except StopIteration: がこの合図を受け止めて break し、while True: のループを抜けています。本物の for も同じで、StopIteration を受け止めると静かにループを終えます。だから画面には何も出ません。

JS の for…of も、同じ役目を { value, done: true } という戻り値で表しています。JS は「終わったかどうか」を戻り値に入れ、Python は「例外を投げる」という形を選んだ、という違いです。

係は一度尽きると、それっきりです。同じ係でもう一度 for を回しても、何も出てきません。リストを回し直すと新しい係が作られるので、何度でも回せます。

python
>>> it = iter(cities)
>>> list(it)
['東京', '大阪', '札幌']
>>> list(it)
[]
  • JS から来た人の Python 06 ― 繰り返し・分岐・例外
  • 7 / 22
  • for の裏側 ④:尽きたら StopIteration で終わる
  • cities(リスト)
  • 更新
  • for の中身
  • (中身は実行されない)
  • 東京
  • 大阪
  • 札幌
  • iter()
  • 取り出し係
  • next()
  • イテレータ
  • StopIteration
  • もう無い
  • # for city in cities: print(city) の中身
  • $ python3 tenki.py
  • it = iter(cities)
  • while True:
  • 東京
  • try:
  • 大阪
  • city = next(it)
  • 札幌
  • except StopIteration:
  • break
  • print(city)
  • StopIteration はエラーではなく「おしまい」の合図。
  • for が受け止めるので、画面には出ない。
  • ひらめき
  • もう出すものが無いと、next() は StopIteration という例外を投げる。for はそれを合図に抜ける。

回せるものは、リストだけではない

iter() に応える物なら、何でも for で回せます。よく使うものを図の表にまとめました。

for で回すもの1回ごとに出てくるもの
list(リスト)・tuple(タプル)要素を前から順に
str(文字列)1文字ずつ("晴れ" なら「晴」「れ」)
dict(辞書)キー。値ではない
range(3)0, 1, 2
開いたファイル1行ずつ(行末の改行 \n 付き)
set(集合)要素。順番は決まっていない
int(数)回せない。TypeError になる

辞書を回すとキーが出てくるのは、JS の for…in に似ています。値も欲しいときは次に出てくる items() を使います。

数を数えたいとき、JS では for (let i = 0; i < 3; i++) と書きました。Python にはこの形の for がありません。代わりに `range()` を回します。

python
for i in range(3):          # 0, 1, 2
    print(i)
for i in range(1, 10, 3):   # 1, 4, 7(1から、10の手前まで、3ずつ)
    print(i)

range(3) は、[0, 1, 2] というリストを作るわけではありません。REPL で見ると range(0, 3) と表示されます。「0から3の手前まで」という決まりだけを持った小さな物で、next() を頼まれるたびに次の数を計算して返します。だから range(10**9) と書いても、10億個の数でメモリがあふれることはありません。

中身をまとめて見たいときは list(range(3)) のようにリストに変換します。結果は [0, 1, 2] です。

  • JS から来た人の Python 06 ― 繰り返し・分岐・例外
  • 8 / 22
  • 回せるものは、リストだけではない
  • for で回すもの
  • 1回ごとに出てくるもの
  • 例
  • list(リスト)
  • 要素を前から順に
  • for c in ["東京", "大阪"]:
  • str(文字列)
  • 1文字ずつ
  • for ch in "晴れ": → 晴、れ
  • dict(辞書)
  • キー(値ではない)
  • for k in weather:
  • range(3)
  • 0, 1, 2(リストは作らない)
  • for i in range(3):
  • ファイル
  • 1行ずつ(改行付き)
  • for line in f:
  • set(集合)
  • 要素(順番は決まっていない)
  • for x in {1, 2}:
  • int(数)
  • 回せない → TypeError
  • for i in 5: → エラー
  • for i in range(3): # JS の for (let i = 0; i < 3; i++) と同じ回り方
  • ひらめき
  • iter() に応える物なら何でも for で回せる。数を数えたいときは range() を回す。

番号も欲しい・並べて回したい・辞書を回したい

for を使っていると、よく次の3つが欲しくなります。どれも組み込みの道具で書けます。

道具何を出すかJS で近いもの
enumerate(a, 1)(番号, 要素) の組。第2引数は始まりの番号a.forEach((c, i) => …)
zip(a, b)(a の要素, b の要素) の組組み込みは無い。添字で合わせる
d.items()(キー, 値) の組Object.entries(obj)

実際に動かした結果が図のとおりです。

python
cities = ["東京", "大阪", "札幌"]
temps = [21.5, 24.0, 15.2]
for i, c in enumerate(cities, 1):
    print(i, c)          # 1 東京 / 2 大阪 / 3 札幌
for c, t in zip(cities, temps):
    print(c, t)          # 東京 21.5 / 大阪 24.0 / 札幌 15.2

for i, c in … のように、名前を2つ並べて受け取っているのに注目してください。enumerate が出すのは (1, "東京") というタプル(第4章)1つです。それを i と c に分けて受け取っています。これをアンパック(分けて受け取ること)と呼びます。JS の分割代入 const [i, c] = [1, "東京"] と同じ考え方です。

zip は、短いほうが尽きたところで止まります。

python
>>> list(zip(cities, [1, 2]))
[('東京', 1), ('大阪', 2)]

札幌が黙って消えました。長さが違うとエラーにならずに一部が捨てられるので、長さが同じはずのものを並べるときは気をつけてください(長さが違ったらエラーにしたいときは zip(a, b, strict=True) と書けます)。

「番号が欲しいから」と for i in range(len(cities)): と書いて cities[i] を取り出す人がいますが、Python では enumerate を使うのがふつうです。

  • JS から来た人の Python 06 ― 繰り返し・分岐・例外
  • 9 / 22
  • 番号も欲しい・並べて回したい・辞書を回したい
  • enumerate:番号と一緒に
  • zip:2つを横に並べて
  • items():キーと値の組
  • for i, c in enumerate(cities, 1):
  • for c, t in zip(cities, temps):
  • for k, v in weather.items():
  • print(i, c)
  • print(c, t)
  • print(k, v)
  • # 1 東京
  • # 東京 21.5
  • # 東京 21.5
  • # 2 大阪
  • # 大阪 24.0
  • # 大阪 24.0
  • # 3 札幌
  • # 札幌 15.2
  • JS:forEach((c, i) => …)
  • JS:組み込みは無い(添字で合わせる)
  • JS:Object.entries(obj)
  • 「i, c」と並べて受け取るのはアンパック(分けて受け取る)。JS の分割代入 const [i, c] = … と同じ考え方
  • zip は短いほうに合わせて止まる。長さが違ってもエラーにならないので注意
  • ひらめき
  • enumerate で番号付き、zip で2つを並べて、items() でキーと値の組。受け取りは「i, c」と並べる。

while・break・continue、それと for の else

`while` は、条件が真の間だけ繰り返します。JS の while と同じです。

break(ループを抜ける)と continue(その回の残りを飛ばして次の回へ)も JS と同じ意味です。図の左のコードでは、n が2のときは continue で print を飛ばし、4のときは break で抜けるので、出力は「1」と「3」だけになります。

気をつけたいのは、Python には `n++` が無いことです。n += 1 と書きます。

JSPython
n++ / n--n += 1 / n -= 1
while (n < 5) { … }while n < 5:
do { … } while (…)無い。while True: と break で書く

図の右は、JS には無い `for … else` です。else の中は「`break` せずに最後まで回り切ったとき」だけ実行されます。

python
for c in cities:
    if c == "名古屋":
        print("見つけた")
        break
else:
    print("見つからなかった")

名古屋はリストに無いので、break されずにループが終わり、「見つからなかった」が出ます。「探して、見つからなかったら…」を、目印の変数(found = False など)無しで書けるのが利点です。

ただ、if の else と見間違えやすく、Python に慣れた人でも読み違えることがあります。使うなら短いループだけにしておくのが無難です。

  • JS から来た人の Python 06 ― 繰り返し・分岐・例外
  • 10 / 22
  • while・break・continue、それと for の else
  • while と break・continue
  • for の else(抜けなかったときだけ)
  • n = 0
  • for c in cities:
  • while n < 5:
  • if c == "名古屋":
  • n += 1
  • print("見つけた")
  • if n == 2:
  • break
  • continue # 残りを飛ばして次の回へ
  • else:
  • if n == 4:
  • print("見つからなかった")
  • break # ループを抜ける
  • # 出力: 見つからなかった
  • print(n)
  • # 出力: 1 と 3
  • for の else は「break せずに最後まで回り切ったとき」だけ実行される。JS には無い書き方
  • 読み慣れない人も多いので、使うなら短いループだけにするのが無難
  • ひらめき
  • while は条件が真の間だけ回る。break で抜け、continue で次の回へ。n++ は無いので n += 1。

match:switch に似ているが、形で分ける

Python 3.10 から `match` 文が使えるようになりました。見た目は JS の switch に似ています。

図の左は、天気の番号(0なら快晴、1〜3なら晴れ…)を言葉に変える describe() です。この本の tenki で使う番号は、気象の世界で広く使われている天気の番号の一部を借りたものだと思ってください。

違いJS の switchPython の match
当たった後break が無いと下の case に落ちる1つだけ実行して終わる。落ちない
「または」case を縦に並べて落とす縦棒でつなぐ(図の左の case 1〜3 の行)
「それ以外」default:case _:
比べ方===値のほか「形」でも比べられる

JS の switch でよくある「break を書き忘れて、次の case まで実行された」事故は、Python では起きません。

match の本領は、値の形で分けられることです。たとえば tenki にコマンドを付けたくなったとします。

python
def handle(cmd):
    match cmd.split():
        case ["now", city]:
            return f"{city} の今の天気"
        case ["week", city, *rest]:
            return f"{city} の週間 {rest}"
        case _:
            return "使い方: now 都市"

handle("now 東京") は「東京 の今の天気」を返します。case ["now", city]: は「要素が2つのリストで、1つ目が "now"」のときに当たり、2つ目を city という名前で取り出します。当たるかどうかの判定と、中身の取り出しが一度にできるのです。

とはいえ、ただの値の分岐なら if / elif でも十分です。match は Python 3.10 より前の版では動かないことも覚えておいてください。

  • JS から来た人の Python 06 ― 繰り返し・分岐・例外
  • 11 / 22
  • match:switch に似ているが、形で分ける
  • Python(3.10 から)
  • JS の switch
  • def describe(code):
  • match code:
  • switch (code) {
  • case 0:
  • case 0:
  • return "快晴"
  • return "快晴";
  • case 1 | 2 | 3:
  • case 1:
  • return "晴れ"
  • case 2: // 下へ落ちる
  • case 61 | 63 | 65:
  • case 3:
  • return "雨"
  • return "晴れ";
  • case _:
  • default:
  • return "不明"
  • return "不明";
  • }
  • match は値だけでなく「形」でも分けられる。case ["now", city]: は「2つの要素で、1つ目が now」に当たり、
  • 2つ目を city に取り出す。ただの値の分岐なら if/elif でも十分
  • ひらめき
  • case は上から1つだけ当たり、下へ落ちない(break 不要)。| で「または」、_ で「それ以外」。

例外:うまくいかないことを「投げて」知らせる

ここからは、失敗の扱いです。

tenki で、データの無い都市「名古屋」を表示しようとしたとします。図の右のコードでは、main() が show("名古屋") を呼び、show の中で辞書 WEATHER["名古屋"] を読もうとします。

辞書に無いキーを [ ] で読むと、Python は値を返す代わりに `KeyError` という例外(exception)を投げます。JS ではオブジェクトに無いキーを読むと undefined が返るだけですが、Python ははっきり失敗として知らせるのです。

やったことJSPython
無いキーを読むundefined が返るKeyError
範囲外の添字undefined が返るIndexError
数にできない文字列Number("はれ") は NaNfloat("はれ") は ValueError

例外が投げられると、その行で処理が止まり、例外は呼んだ側へ遡っていきます。図の左の積み重ねで見ると、③の WEATHER["名古屋"] で投げられた KeyError(赤い小包)は、まず②の show() に戻ります。show の中には受け止める仕組みが無いので、show の残りの行(print)は実行されず、さらに上の①の main() へ遡ります。

この「呼んだ側の積み重ね」を呼び出しスタックと呼びます。例外は、受け止めてくれる場所が見つかるまで、スタックを1段ずつ上がっていきます。

無いキーで例外にしたくないときは、WEATHER.get("名古屋") と書くと、例外の代わりに None が返ります(第4章)。

  • JS から来た人の Python 06 ― 繰り返し・分岐・例外
  • 12 / 22
  • 例外:うまくいかないことを「投げて」知らせる
  • 呼び出しの積み重ね(上が呼んだ側)
  • main()
  • def show(city):
  • code, temp = WEATHER[city]
  • try: の中で show(city) を呼んだ
  • print(f"{city}: {temp}℃")
  • def main():
  • for city in ["東京", "名古屋"]:
  • show("名古屋")
  • try:
  • WEATHER[city] を読みにいく
  • show(city)
  • except KeyError:
  • print(city, "はデータなし")
  • WEATHER["名古屋"] → キーが無い
  • KeyError
  • 例外は、受け止める except が見つかるまで
  • 呼んだ側へ遡っていく
  • ひらめき
  • 辞書に無いキーを読むと KeyError が投げられる。その場で止まり、呼んだ側へ遡っていく。

遡った先に except があれば、そこで受け止める

①の main() では、show(city) の呼び出しを try: で包み、その下に except KeyError: を書いていました。遡ってきた KeyError は、ここで受け止められます。

python
try:
    show(city)
except KeyError:
    print(city, "はデータなし")

受け止めた後は、except の中身(「名古屋 はデータなし」を表示)を実行し、そのまま try 文の次へ進みます。for の中なので、次の都市の処理が続きます。

図で灰色になった②の show() は、途中で打ち切られたままです。print(f"{city}: {temp}℃") の行は実行されていません。

もしどこにも `except` が無かったら、例外はいちばん上(ファイルの一番外側)まで遡り、Python は Traceback(遡ってきた道すじ)を表示してプログラムを止めます。JS で catch されなかったエラーがコンソールに出て処理が止まるのと同じです。

例外が起きた後結果
遡った先に合う except があるそこで受け止め、その後ろから続ける
どこにも無いTraceback を表示してプログラムが終わる
  • JS から来た人の Python 06 ― 繰り返し・分岐・例外
  • 13 / 22
  • 遡った先に except があれば、そこで受け止める
  • 呼び出しの積み重ね(上が呼んだ側)
  • main()
  • def show(city):
  • except KeyError: で受け止めた
  • KeyError
  • code, temp = WEATHER[city]
  • print(f"{city}: {temp}℃")
  • def main():
  • for city in ["東京", "名古屋"]:
  • show("名古屋")
  • try:
  • 残りの行は実行されずに打ち切り
  • show(city)
  • except KeyError:
  • print(city, "はデータなし")
  • WEATHER["名古屋"] → キーが無い
  • 受け止めたら、main はそのまま次の都市へ進む
  • ひらめき
  • show の残りは飛ばされ、main の except が受け止める。誰も受け止めなければプログラムが止まる。

try の4つの枠 ①:うまくいったとき

Python の try 文には、最大で4種類の枠があります。

枠いつ実行されるか
try:まず試す処理
except 型 as e:try の中でその型の例外が起きたとき
else:try の中で例外が起きなかったとき
finally:どちらの場合も必ず最後に

図の左の parse() は、文字列を小数に変換する関数です。parse("21.5") を実行すると、float("21.5") は成功します。

このときの道すじは、図の右の緑の矢印のとおりです。try の中が最後まで通ったので except は飛ばされ、else の「成功: 21.5」、finally の「必ず通る」の順に実行されて、return t に進みます。左下の出力が実際に動かした結果です。

else は JS には無い枠です。「成功したときだけやること」は try の中の最後に書いても動きます。わざわざ else に分けるのは、`try` で包む範囲を、失敗しうる行だけに絞るためです。try の中に行を詰め込むと、別の行で起きた同じ型の例外まで、うっかり受け止めてしまいます。

  • JS から来た人の Python 06 ― 繰り返し・分岐・例外
  • 14 / 22
  • try の4つの枠 ①:うまくいったとき
  • def parse(s):
  • try:
  • try:試す
  • t = float(s)
  • except ValueError as e:
  • print("失敗:", e)
  • t = None
  • except:失敗したら
  • else:成功したら
  • else:
  • print("成功:", t)
  • finally:
  • print("必ず通る")
  • return t
  • finally:必ず
  • >>> parse("21.5")
  • 成功: 21.5
  • 必ず通る
  • return t へ進む
  • t = 21.5
  • ひらめき
  • try の中が最後まで通ると except は飛ばされ、else、finally の順に通る。

try の4つの枠 ②:失敗したとき

今度は parse("はれ") です。float("はれ") は小数にできないので、`ValueError` を投げます。

道すじは図の赤い矢印です。try の中の残りは飛ばされ、except ValueError as e: が受け止めます。as e と書くと、投げられた例外そのものに e という名前が付き、中のメッセージを読めます。

python
>>> parse("はれ")
失敗: could not convert string to float: 'はれ'
必ず通る

else は飛ばされ、finally は失敗したときも通ります。finally は、開いたものを閉じる、一時ファイルを消す、といった「何があってもやる後片付け」を書く場所です。

finally が通るのは、ここで挙げた2つの場合だけではありません。

起きたことfinally
成功した通る
except で受け止めた通る
受け止められない型の例外が起きた通ってから、例外が呼んだ側へ遡る
try の中で return した通ってから、関数を抜ける

JS の finally とまったく同じ考え方です。

  • JS から来た人の Python 06 ― 繰り返し・分岐・例外
  • 15 / 22
  • try の4つの枠 ②:失敗したとき
  • def parse(s):
  • try:
  • try:試す
  • t = float(s)
  • ValueError
  • except ValueError as e:
  • print("失敗:", e)
  • t = None
  • except:失敗したら
  • else:成功したら
  • else:
  • print("成功:", t)
  • finally:
  • print("必ず通る")
  • return t
  • finally:必ず
  • >>> parse("はれ")
  • 失敗: could not convert string to float: 'はれ'
  • 必ず通る
  • return t へ進む
  • ひらめき
  • float("はれ") が ValueError を投げる。except が受け止め、else は飛ばし、finally は通る。

JS の try/catch との違い:型ごとに受け分ける

JS と Python の例外の扱いを並べると、図の表のようになります。いちばん大きな違いは受け分け方です。

JS では catch (e) を1つだけ書き、中で「どんなエラーか」を調べます。

js
try {
  show(city);
} catch (e) {
  if (e instanceof TypeError) { /* … */ } else { throw e; }
}

Python では、`except` を型ごとに並べ、上から順に見て、合った最初の1つに入ります。合うものが無ければ、例外はそのまま呼んだ側へ遡ります。

python
try:
    show(city)
except KeyError:
    print("データなし")
except (ValueError, TypeError) as e:
    print("データが壊れている:", e)

「合う」とは、その型か、その子孫の型であることです。Python の例外には親子関係があり、実際に調べるとこうなっています。

python
>>> KeyError.__mro__
(<class 'KeyError'>, <class 'LookupError'>, <class 'Exception'>, <class 'BaseException'>, <class 'object'>)

KeyError の親は LookupError、その親は Exception です。だから except LookupError: と書けば KeyError も IndexError も受け止められ、except Exception: と書けばふつうの例外はほぼすべて受け止められます。

JS では文字列でも何でも throw できますが、Python で raise できるのは `BaseException` の子孫だけです。raise "失敗" と書くと、それ自体が TypeError になります。

  • JS から来た人の Python 06 ― 繰り返し・分岐・例外
  • 16 / 22
  • JS の try/catch との違い:型ごとに受け分ける
  • JS
  • Python
  • 形
  • try {…} catch (e) {…} finally {…}
  • try: / except 型 as e: / else: / finally:
  • 種類で分ける
  • catch の中で instanceof を調べる
  • except KeyError: を型ごとに並べる
  • 全部受ける
  • catch (e)
  • except Exception as e:
  • 投げる
  • throw new Error("…")
  • raise ValueError("…")
  • 投げられる物
  • 何でも(文字列でも)
  • BaseException の子孫だけ
  • 成功時だけ
  • 無い(try の最後に書く)
  • else:
  • except KeyError: は KeyError とその子孫に当たる(KeyError は LookupError の子、その親は Exception)
  • except (ValueError, TypeError): のようにタプルでまとめて受けることもできる
  • ひらめき
  • JS は catch を1つ書いて中で調べる。Python は except を型ごとに並べ、合う最初の1つに入る。

自分で raise する・自分の例外を作る

例外は、自分で投げることもできます。JS の throw に当たるのが `raise` です。

python
raise ValueError("気温が読めません")

JS の throw new Error("…") と違い、new は書きません。Python ではクラスを呼ぶだけで物が作られるからです(第7章)。

図の左のように、`Exception` を親にしたクラスを作れば、自分専用の例外になります。中身は pass(何もしない)で十分です。

python
class CityNotFound(Exception):
    pass

こうしておくと、呼ぶ側は except CityNotFound: と書いて、「都市が見つからない」失敗だけを狙って受け止められます。KeyError のような汎用の例外と混ざりません。

誰も受け止めないと、図の右のような Traceback が表示されてプログラムが止まります(実際の表示にはファイルの完全なパスや、どこで起きたかを示す ~~~^^^ の印も出ます)。

Traceback は下から読むのが基本です。

行読み取れること
いちばん下何の例外で、どんなメッセージか(CityNotFound: 都市が見つかりません: 名古屋)
その上例外が投げられた場所(lookup の7行目)
さらに上そこへ至るまでの呼び出しの道すじ(11行目で lookup を呼んだ)

Traceback の読み方は第18章でもう一度まとめます。

  • JS から来た人の Python 06 ― 繰り返し・分岐・例外
  • 17 / 22
  • 自分で raise する・自分の例外を作る
  • class CityNotFound(Exception):
  • 誰も受け止めないと、止まってこう出る
  • pass
  • Traceback (most recent call last):
  • def lookup(city):
  • File "tenki.py", line 11, in <module>
  • table = {"東京": (35.68, 139.69)}
  • lookup("名古屋")
  • if city not in table:
  • File "tenki.py", line 7, in lookup
  • raise CityNotFound(
  • raise CityNotFound(
  • f"都市が見つかりません: {city}")
  • f"都市が見つかりません: {city}")
  • return table[city]
  • CityNotFound: 都市が見つかりません: 名古屋
  • lookup("名古屋")
  • いちばん下が、何が起きたか
  • 上の行ほど、そこまでの呼び出しの道すじ
  • クラスの書き方は第7章。いまは「Exception を親にした名前を1つ作れる」とだけ覚えておけば十分
  • ひらめき
  • raise で投げる。Exception を継承したクラスを作れば、自分専用の例外になる。

with:後片付けを忘れさせない書き方

ファイルを開いたら、使い終わったら閉じる必要があります。閉じ忘れると、開いたままのファイルが溜まっていきます。また、書き込んだ内容はいったんメモリに溜められ(バッファ)、閉じたときなどにまとめてファイルへ書き出されるので、閉じないと反映が遅れることがあります。

確実に閉じるには、図の左下のように try / finally で包めば済みます。途中で例外が起きても finally は必ず通るからです。ただ、毎回これを書くのは面倒で、忘れがちです。

そこで Python には `with` 文があります。

python
with open("cities.txt", encoding="utf-8") as f:
    for line in f:
        print(line.strip())
print(f.closed)   # True

with のブロックを抜けた瞬間、ファイルは必ず閉じられます。ブロックの中で例外が起きても、return で抜けても同じです。実際に動かすと、最後の f.closed は True(閉じている)になります。

open() に encoding="utf-8" を付けているのは、日本語のファイルを読むためです。付けないと OS の既定の文字コードが使われ、Windows などで文字化けやエラーになることがあります。

図の右は、Node.js で同じことをした例です。fs/promises の open() で得たファイルを、try / finally で閉じています。JS では、この try / finally を自分で書くのがふつうです。

JS(Node.js)Python
確実に閉じるtry { … } finally { await fh.close(); }with open(…) as f:
閉じるのを書くのは自分ファイルの側(次で見る)
  • JS から来た人の Python 06 ― 繰り返し・分岐・例外
  • 18 / 22
  • with:後片付けを忘れさせない書き方
  • with を使う(ふつうはこちら)
  • JS(Node.js)で同じことをすると
  • with open("cities.txt", encoding="utf-8") as f:
  • for line in f:
  • import { open } from "node:fs/promises";
  • print(line.strip())
  • const fh = await open("cities.txt");
  • # ここで f は閉じている
  • try {
  • print(f.closed) # True
  • // 読む
  • } finally {
  • await fh.close();
  • with を使わないと、こう書くことになる
  • }
  • f = open("cities.txt", encoding="utf-8")
  • try:
  • ...
  • finally:
  • 閉じ忘れると、開いたままのファイルが溜まる。
  • f.close()
  • 書いた内容のファイルへの反映が遅れることもある。
  • ひらめき
  • with のブロックを出た瞬間、ファイルは必ず閉じられる。途中で例外が起きても閉じる。

with の裏側:入るとき __enter__、出るとき __exit__

with は、ファイル専用の仕組みではありません。`__enter__` と `__exit__` という2つのメソッドを持つ物なら、何でも with に渡せます。こういう物をコンテキストマネージャーと呼びます。

with X() as t: で起きることを、図の番号の順に追います。

順番起きること
1X() の __enter__() が呼ばれ、戻り値に t という名前が付く
2with の中身が実行される
3中身をどう抜けても(ふつうに終わっても、例外でも、return でも)__exit__() が呼ばれる

図の左は、この2つを自分で書いた Timer です。中身の途中で ValueError を投げても、実行結果(左下)のとおり「片付ける」が出ています。__exit__ には、起きた例外の型(<class 'ValueError'>)も渡されます。ふつうに終わったときは None が渡されます。

__exit__ が済んだ後、例外はそのまま外へ遡ります。with は例外を握りつぶすわけではなく、「外へ出る前に必ず片付けを挟む」だけです(__exit__ が True を返すと例外を止められますが、ふつうはしません)。

open() が返すファイルも、この2つを持っています。ファイルの __exit__ がやっていることが close() です。つまり with とは、前の図の try / finally を、相手の物に書いておいてもらう仕組みです。

__init__ や __exit__ のように、前後に __ が2つずつ付いた名前は特殊メソッドと呼ばれ、Python が決まった場面で自動で呼びます。第7章で詳しく見ます。

  • JS から来た人の Python 06 ― 繰り返し・分岐・例外
  • 19 / 22
  • with の裏側:入るとき __enter__、出るとき __exit__
  • class Timer:
  • def __enter__(self):
  • 再生
  • __enter__()
  • print("始める")
  • 準備。as の名前に入る
  • return self
  • def __exit__(self, typ, exc, tb):
  • print("片付ける", typ)
  • with Timer() as t:
  • コード
  • with の中身
  • print("中身")
  • ここで例外が起きても…
  • ValueError
  • raise ValueError("途中で失敗")
  • 始める
  • 中身
  • チェック
  • __exit__()
  • 例外はそのまま外へ
  • 片付ける <class 'ValueError'>
  • 必ず呼ばれる片付け
  • open() が返すファイルも、この2つを持っている。__exit__ の中身が close()。第7章の特殊メソッドの一種
  • ひらめき
  • with は相手の __enter__ を呼んでから中身を実行し、どう抜けても最後に __exit__ を呼ぶ。

tenki に入れる:3都市を回し、1都市の失敗で止めない

ここまでの道具を tenki に入れます。まだ本物の天気を取りにはいかず(それは第11章で requests を入れてから)、辞書 WEATHER に仮のデータを置いています。describe() は match の例で書いたものです。

python
def main(cities):
    failed = 0
    for city in cities:
        try:
            show(city)
        except KeyError:
            print(f"{city}: データがありません")
            failed += 1
    return failed

実際に動かした結果が図の右です。名古屋でデータが無くても、大阪の表示まで進んでいます。最後の 1 は、失敗した都市の数(main の戻り値)です。

大事なのは、`try` で包む範囲を「1都市ぶん」にしていることです。

try の位置名古屋で失敗したとき
for の中(1都市ずつ)名古屋だけ飛ばして、大阪へ進む
for の外(ループ全体)ループごと抜けて、大阪は表示されない

もう1つ、受け止めているのは KeyError だけです。except Exception: と広く書くと、show の中の書き間違い(たとえば変数名の打ち間違いで起きる NameError)まで「データがありません」と表示されてしまい、本当の原因が見えなくなります。受け止める例外は、想定している失敗だけに絞るのが基本です。

  • JS から来た人の Python 06 ― 繰り返し・分岐・例外
  • 20 / 22
  • tenki に入れる:3都市を回し、1都市の失敗で止めない
  • WEATHER = {"東京": (0, 21.5), "大阪": (63, 24.0)}
  • 動かすと
  • def show(city):
  • >>> main(["東京", "名古屋", "大阪"])
  • code, temp = WEATHER[city]
  • 東京: 快晴 21.5℃
  • print(f"{city}: {describe(code)} {temp}℃")
  • 名古屋: データがありません
  • 大阪: 雨 24.0℃
  • def main(cities):
  • failed = 0
  • 1
  • for city in cities:
  • try:
  • show(city)
  • describe() は match の例のもの。
  • except KeyError:
  • print(f"{city}: データがありません")
  • try で包むのは「1都市ぶん」だけ。
  • failed += 1
  • ループ全体を包むと、名古屋で
  • return failed
  • 残りの都市まで止まってしまう。
  • ひらめき
  • for で都市を回し、1都市ずつ try で包む。失敗した都市だけ知らせて、次の都市へ進む。

よくある間違いと、そのときのメッセージ

この章の内容で、JS から来た人がよくつまずく所をまとめました。エラーの最後の行(型の名前とメッセージ)を読めば、たいてい原因の見当がつきます。

出るもの原因直し方
TypeError: 'int' object is not iterablefor i in len(cities): と数を回そうとしたrange(len(…))、ふつうは enumerate
SyntaxError: expected ':'if・for・def の行末のコロン忘れ行末に : を付ける
IndentationError字下げの幅がそろっていないスペース4つにそろえる
エラーは出ないが要素が飛ばされる回している最中のリストから remove した新しいリストを作る(第4章の内包表記)
Ctrl+C でも止まらないexcept: とだけ書いて全部受け止めたexcept 型: と具体的に書く
失敗が黙って消えるexcept の中を pass だけにした知らせるか、raise で投げ直す

回している最中に remove すると、たとえば [1, 2, 2, 3] から2を消そうとして [1, 2, 3] が残ります。取り出し係は「次は何番目」と位置だけで進むので、要素が詰められると1つ飛ばしてしまうのです(iter の図の仕組みそのままです)。

except: と型を書かないと、Ctrl+C で投げられる KeyboardInterrupt まで受け止めてしまいます。これは Exception ではなく BaseException の直接の子なので、except Exception: なら受け止めずに済みます。

except の中で raise とだけ書くと、受け止めた例外をそのまま投げ直せます。「記録だけして、処理は呼んだ側に任せる」ときに使います。

  • JS から来た人の Python 06 ― 繰り返し・分岐・例外
  • 21 / 22
  • よくある間違いと、そのときのメッセージ
  • 出るもの
  • 原因
  • 直し方
  • TypeError: 'int' object is not iterable
  • for i in len(cities):
  • range(len(…)) か enumerate
  • SyntaxError: expected ':'
  • if・for の行末のコロン忘れ
  • 行末に : を付ける
  • IndentationError
  • 字下げの幅がそろっていない
  • スペース4つにそろえる
  • エラーは出ないが要素が飛ぶ
  • 回している最中に remove
  • 新しいリストを作る(第4章)
  • Ctrl+C でも止まらない
  • except: だけで全部受けた
  • except 型: と具体的に書く
  • 失敗が黙って消える
  • except で pass だけ書いた
  • 知らせるか、投げ直す(raise)
  • ひらめき
  • エラーの最後の行を読めば、たいてい原因が書いてある。まずは型の名前を見る。

まとめ:流れを変える道具と、失敗の受け止め方

この章で出てきた道具を、JS と並べてまとめます。

書き方何をするJS では
if / elif / else条件で分けるif / else if / else
for x in 何か:iter() と next() で1つずつ取り出すfor…of
range・enumerate・zip・items数える・番号付き・並べる・キーと値for (let i…)・添字・Object.entries
while / break / continue条件の間だけ回す同じ
match / case値や形で分ける(下へ落ちない)switch(落ちる)
try / except / else / finally例外を型ごとに受け止めるtry / catch / finally
raise例外を投げるthrow
with__enter__ と __exit__ で後片付けを保証try / finally を自分で書く

覚えておきたいのは2つの流れです。for は、相手から取り出し係をもらい、`StopIteration` が来るまで `next()` を呼び続けること。例外は、受け止める `except` が見つかるまで呼び出しを遡ること。この2つがわかっていれば、見慣れない書き方に出会っても何が起きているかを追えます。

さて、tenki のデータは今、(0, 21.5) のようなタプルで持っています。code, temp = WEATHER[city] と取り出していますが、「0番目が天気の番号、1番目が気温」という約束は、コードを読む人の頭の中にしかありません。都市の名前や湿度も一緒に持ちたくなったら、どんどん分かりにくくなります。

都市・天気・気温を1つの「物」にまとめて、`w.temp` のように名前で読めないのか。表示する関数もその物に持たせられないのか。 JS ならクラスやオブジェクトを使うところです。次の第7章では、Python のクラスを見ます。JS の this と Python の self の違いが、最初の山場です。

  • JS から来た人の Python 06 ― 繰り返し・分岐・例外
  • 22 / 22
  • まとめ:流れを変える道具と、失敗の受け止め方
  • 書き方
  • 何をする
  • JS では
  • if / elif / else
  • 条件で分ける
  • if / else if / else
  • for x in 何か:
  • iter() と next() で1つずつ
  • for…of
  • range・enumerate・zip
  • 数える・番号付き・並べる
  • for (let i…)・添字
  • while / break / continue
  • 条件の間だけ回す
  • 同じ
  • match / case
  • 値や形で分ける(落ちない)
  • switch(落ちる)
  • try / except / else / finally
  • 例外を型ごとに受ける
  • try / catch / finally
  • raise
  • 例外を投げる
  • throw
  • with
  • __enter__ と __exit__ で後片付け
  • try / finally
  • ひらめき
  • 次の疑問:都市・気温・天気が別々の値のまま。まとめて1つの物にできないか → 第7章(クラス)。

0 件のコメント

読み込み中…

権利侵害・不適切な内容の連絡: [email protected]