目次
3 章

制御フロー — if, while, for, match-case

インデントでブロックを作る制御フロー、range/enumerate/zip、そして switch とは性格の異なる match-case のパターンマッチまで整理します。

第2章 変数、基本型と型ヒント で作った変数を持って、本章では 制御の流れ を組み立てます。ifwhilefor、そして 3.10 で入った match-case までです。

後に頻繁に登場する match-case が本章の核です。他の言語の switch とは動作が少し違うため最初は不慣れですが、一度馴染めば JSON / イベント / DTO の分岐コードが短く安全になります。第13章 パターンマッチの深層 で改めて詳しく扱います。

Python の制御フローの大きな特徴は二つあります。

  1. ブロックはインデントで区切ります。 中括弧 {} はありません。
  2. コロン(:) でヘッダを終えます。 if x > 0: のように。

そのため、インデントがそのまま文法になります。一般的には空白 4 つを使います (PEP 8)。タブとスペースを混ぜて使うとエラーになります。

if / elif / else #

他の言語ともっとも似ています。括弧がないのが違いです。

if 基本
score = 85

if score >= 90:
    grade = "A"
elif score >= 80:
    grade = "B"
elif score >= 70:
    grade = "C"
else:
    grade = "F"

print(grade)   # B

else if ではなく elif です。歴史的な理由でこうなっているので、まずはそのまま覚えておけば十分です。

一行版 — 三項演算子 #

C 系の cond ? a : b とは違います。Python は a if cond else b の形です。

三項
status = "成人" if age >= 19 else "未成年"

自然言語に近い順序なので最初は不慣れですが、慣れると読みやすいです。

真偽値と条件 — falsy の活用 #

第2章 で見た falsy 値をそのまま活用します。

falsy 条件
items: list[int] = []

if not items:
    print("空")    # これが出力される

# 下も同じ意味だが、上の方が Python らしい
if len(items) == 0:
    print("空")

リストが空かの判定で len(x) == 0 を使うのは、通常 not x に短縮します。

is== #

ここは注意が必要な部分です。

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 はオブジェクト同一性比較 です。NoneTrueFalse は常に is で比較してください。それ以外の値は == です。

while #

条件が真である間繰り返します。

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

++ はありません。count += 1 です。

breakcontinue #

break / continue
n = 0
while True:
    n += 1
    if n % 2 == 0:
        continue       # 偶数はスキップ
    if n > 7:
        break          # 7 を超えたら終了
    print(n)
# 1, 3, 5, 7

while True + break は、「条件が複雑で上に書きにくい」無限ループの定番パターンです。

else 節 — あまり知られていない機能 #

whilefor には else が付けられます。 「ループが break なしで終わったとき」に実行されます。

while-else
n = 0
while n < 10:
    if n == 99:
        break
    n += 1
else:
    print("break なしで終了した")    # これが出力

頻繁ではありませんが、「探している値がなくて最後まで回ったとき」を処理するのに、書き味がきれいになる場面があります。使いすぎるとかえって読みにくくなるので、ほどほどに。

for — すべてシーケンス走査 #

他の言語の for (i = 0; i < n; i++) のような C スタイルは ありません。 Python の for は常に イテラブルを走査 します。

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 — 数値シーケンスが必要なとき #

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 — 複数リスト同時走査 #

zip
names = ["カーティス", "スミス", "ジョン"]
ages = [30, 25, 40]

for name, age in zip(names, ages):
    print(f"{name}: {age}")

長さが違うと短い方に合わせて切られます。長さが合わないときにエラーを出したいなら zip(..., strict=True) (3.10+) です。

strict モード
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: に相当します。

複数の値を一つのケースに — | #

OR パターン
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)

各パターン内の xyその位置の値を変数にバインド します。マッチと同時に変数が作られます。JavaScript の構造分解 + switch が一箇所に合わさった形です。

辞書パターン #

dict パターン
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 で書き直すと、長く不自然になります。

match なしで同じこと
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章 コレクションと内包表記 で詳しく扱います。

練習問題 #

  1. pyright がインストールされたプロジェクトで def grade(score: int) -> str: のシグネチャの関数を書いてください。90 以上 “A”、80 以上 “B”、70 以上 “C”、それ以外 “F” を返します。if-elif-elsematch-case 版の 2 つ を両方書き、動作が同じか確認します。
  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) 関数を書いてください。
  3. names = ["カーティス", "スミス", "ジョン"]ages = [30, 25, 40]enumerate + zip で組み合わせ、0: カーティス(30)1: スミス(25)2: ジョン(40) のように出力してください。長さが違うとエラーになるよう strict=True を適用します。

一行まとめ: Python の制御フローはインデント + コロン。if/elif/elsewhilefor in が基本で、for の伴走者 range/enumerate/zip が日常の 90% を占める。match-case は単純な switch ではなくデータの形で分岐する道具であり、JSON / イベント / クラスの分岐で特に役立つ。

次の章 #

次の 第4章 コレクションと内包表記 では、もっともよく使われる 4 つのデータ構造 — listtupledictset と、それらを一行で作る 内包表記 を扱います。本章の for が内包表記の土台になります。

X