制御フロー — if, while, for, match-case
インデントでブロックを作る制御フロー、range/enumerate/zip、そして switch とは性格の異なる match-case のパターンマッチまで整理します。
第2章 変数、基本型と型ヒント で作った変数を持って、本章では 制御の流れ を組み立てます。if、while、for、そして 3.10 で入った match-case までです。
後に頻繁に登場する match-case が本章の核です。他の言語の switch とは動作が少し違うため最初は不慣れですが、一度馴染めば JSON / イベント / DTO の分岐コードが短く安全になります。第13章 パターンマッチの深層 で改めて詳しく扱います。
Python の制御フローの大きな特徴は二つあります。
- ブロックはインデントで区切ります。 中括弧
{}はありません。 - コロン(
:) でヘッダを終えます。if x > 0:のように。
そのため、インデントがそのまま文法になります。一般的には空白 4 つを使います (PEP 8)。タブとスペースを混ぜて使うとエラーになります。
if / elif / else
#
他の言語ともっとも似ています。括弧がないのが違いです。
score = 85
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
else:
grade = "F"
print(grade) # Belse if ではなく elif です。歴史的な理由でこうなっているので、まずはそのまま覚えておけば十分です。
一行版 — 三項演算子 #
C 系の cond ? a : b とは違います。Python は a if cond else b の形です。
status = "成人" if age >= 19 else "未成年"自然言語に近い順序なので最初は不慣れですが、慣れると読みやすいです。
真偽値と条件 — falsy の活用 #
第2章 で見た falsy 値をそのまま活用します。
items: list[int] = []
if not items:
print("空") # これが出力される
# 下も同じ意味だが、上の方が Python らしい
if len(items) == 0:
print("空")リストが空かの判定で len(x) == 0 を使うのは、通常 not x に短縮します。
is と ==
#
ここは注意が必要な部分です。
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # True (値が同じ)
print(a is b) # False (別オブジェクト)
x = None
print(x is None) # True ← None 比較は常に is== は値比較、is はオブジェクト同一性比較 です。None、True、False は常に is で比較してください。それ以外の値は == です。
while
#
条件が真である間繰り返します。
count = 0
while count < 5:
print(count)
count += 1++ はありません。count += 1 です。
break、continue
#
n = 0
while True:
n += 1
if n % 2 == 0:
continue # 偶数はスキップ
if n > 7:
break # 7 を超えたら終了
print(n)
# 1, 3, 5, 7while True + break は、「条件が複雑で上に書きにくい」無限ループの定番パターンです。
else 節 — あまり知られていない機能
#
while、for には else が付けられます。 「ループが break なしで終わったとき」に実行されます。
n = 0
while n < 10:
if n == 99:
break
n += 1
else:
print("break なしで終了した") # これが出力頻繁ではありませんが、「探している値がなくて最後まで回ったとき」を処理するのに、書き味がきれいになる場面があります。使いすぎるとかえって読みにくくなるので、ほどほどに。
for — すべてシーケンス走査
#
他の言語の for (i = 0; i < n; i++) のような C スタイルは ありません。 Python の for は常に イテラブルを走査 します。
names = ["a", "b", "c"]
for name in names:
print(name)文字列もイテラブルです。
for ch in "hello":
print(ch)
# h, e, l, l, o第11章 イテラブル、ジェネレータ、yield from で、for in がどんなオブジェクトで動くのか(__iter__ / __next__ プロトコル) を深く扱います。
range — 数値シーケンスが必要なとき
#
for i in range(5): # 0, 1, 2, 3, 4
print(i)
for i in range(2, 7): # 2, 3, 4, 5, 6
print(i)
for i in range(0, 10, 2): # 0, 2, 4, 6, 8 (step 2)
print(i)range(start, stop, step) です。stop は 含まれません (半開区間) 。最初は迷いますが、他の言語のスライスと同じ慣例なのですぐ慣れます。
enumerate — インデックスが必要なとき
#
items = ["りんご", "なし", "柿"]
for i, item in enumerate(items):
print(f"{i}: {item}")
# 0: りんご
# 1: なし
# 2: 柿for i in range(len(items)) した後 items[i] で取り出すのはアンチパターンです。enumerate ひとことで済みます。
zip — 複数リスト同時走査
#
names = ["カーティス", "スミス", "ジョン"]
ages = [30, 25, 40]
for name, age in zip(names, ages):
print(f"{name}: {age}")長さが違うと短い方に合わせて切られます。長さが合わないときにエラーを出したいなら zip(..., strict=True) (3.10+) です。
for n, a in zip(names, ages, strict=True):
...パターンマッチ — match-case (3.10+)
#
switch と見た目は似ていますが、できることがずっと多いです。 Python のものは単純な分岐ではなく パターンマッチ として設計されています。
もっとも単純な形 #
def http_status(code: int) -> str:
match code:
case 200:
return "OK"
case 404:
return "Not Found"
case 500:
return "Server Error"
case _:
return "Unknown"case _: がワイルドカード(default) です。JavaScript の default: に相当します。
複数の値を一つのケースに — |
#
def category(code: int) -> str:
match code:
case 200 | 201 | 204:
return "成功"
case 400 | 401 | 403 | 404:
return "クライアントエラー"
case 500 | 502 | 503:
return "サーバエラー"
case _:
return "その他"構造分解 — ここから重要 #
リスト、タプル、辞書の 形そのものをパターンとして 使えます。
def describe(point: tuple[int, ...]) -> str:
match point:
case ():
return "空の点"
case (x,):
return f"1D: {x}"
case (x, y):
return f"2D: ({x}, {y})"
case (x, y, z):
return f"3D: ({x}, {y}, {z})"
case _:
return "高次元"
print(describe((1, 2))) # 2D: (1, 2)
print(describe((1, 2, 3))) # 3D: (1, 2, 3)各パターン内の x、y は その位置の値を変数にバインド します。マッチと同時に変数が作られます。JavaScript の構造分解 + switch が一箇所に合わさった形です。
辞書パターン #
event = {"type": "click", "x": 100, "y": 200}
match event:
case {"type": "click", "x": x, "y": y}:
print(f"クリック: ({x}, {y})")
case {"type": "key", "code": code}:
print(f"キー: {code}")
case _:
print("不明なイベント")特定のキーが存在し、その値を変数として取り出すパターンです。API レスポンス処理によく合います。
ガード — if 条件の追加
#
match (x, y):
case (a, b) if a == b:
print(f"同一: {a}")
case (a, b) if a > b:
print(f"a が大きい")
case _:
print("その他")クラスパターン #
class Circle:
def __init__(self, radius: float):
self.radius = radius
class Square:
def __init__(self, side: float):
self.side = side
shape = Circle(5)
match shape:
case Circle(radius=r):
print(f"円、半径 {r}")
case Square(side=s):
print(f"正方形、一辺 {s}")型チェック + 属性抽出が一行に収まります。
switch と性格が違う理由
#
match-case は単純な多重分岐ではなく データの形で分岐する道具 です。上のコードを if-elif で書き直すと、長く不自然になります。
if isinstance(shape, Circle):
r = shape.radius
print(f"円、半径 {r}")
elif isinstance(shape, Square):
s = shape.side
print(f"正方形、一辺 {s}")同じ動作ですが、型チェックと属性取り出しが別々 です。match はこの 2 段階を一行で表現します。JSON / イベントオブジェクト処理で効用が大きいです。
本章では表層だけ見ます。第13章 パターンマッチの深層 でシーケンス / マッピング / クラスパターンの正確な規則と実戦での使いどころを改めて詳しく扱います。
流れの真実 — pass
#
ブロックを空にすることはできません。インデントブロックがなければ文法エラーです。とりあえず空にしておきたいときは pass を使います。
def todo():
pass # 後で実装
if x > 0:
pass # ひとまず無視
else:
handle(x)... (Ellipsis オブジェクト) も同じ用途でよく見ますが、pass の方が慣習的です。
for にも内包表記というパターンがある
#
リスト、辞書、セットを作るときに for を一行に圧縮する記法があります。
squares = [x ** 2 for x in range(5)]
# [0, 1, 4, 9, 16]次の 第4章 コレクションと内包表記 で詳しく扱います。
練習問題 #
pyrightがインストールされたプロジェクトでdef grade(score: int) -> str:のシグネチャの関数を書いてください。90 以上 “A”、80 以上 “B”、70 以上 “C”、それ以外 “F” を返します。if-elif-else版 とmatch-case版の 2 つ を両方書き、動作が同じか確認します。dict[str, int | str]型のevent変数に{"type": "click", "x": 100, "y": 200}または{"type": "key", "code": "Enter"}または{"type": "scroll", "dx": 0, "dy": -10}の 3 つのうち 1 つが入ります。match-caseと dict パターンで分岐し、それぞれ異なるメッセージを出力するhandle(event)関数を書いてください。names = ["カーティス", "スミス", "ジョン"]とages = [30, 25, 40]をenumerate+zipで組み合わせ、0: カーティス(30)、1: スミス(25)、2: ジョン(40)のように出力してください。長さが違うとエラーになるようstrict=Trueを適用します。
一行まとめ: Python の制御フローはインデント + コロン。
if/elif/else、while、for inが基本で、forの伴走者range/enumerate/zipが日常の 90% を占める。match-caseは単純な switch ではなくデータの形で分岐する道具であり、JSON / イベント / クラスの分岐で特に役立つ。
次の章 #
次の 第4章 コレクションと内包表記 では、もっともよく使われる 4 つのデータ構造 — list、tuple、dict、set と、それらを一行で作る 内包表記 を扱います。本章の for が内包表記の土台になります。