モダンPython基礎 #3 制御フロー — if, while, for, match-case
#2 変数、基本型、型ヒント で作った変数たちを使い、今回は 流れ を作っていきます。if、while、for、そして 3.10 で入った match-case までです。
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("空です") # これが出力される
# 下も同じ意味だが、上の方が Pythonic
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, orange — 数値のシーケンスが必要なとき
#
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 はこの二段階を合わせて一行で表現します。JSON / イベントオブジェクト処理で効果が大きくなります。
流れの真実 — 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 コレクションと内包表記) で本格的に扱います。
まとめ #
今回見たもの:
- インデントでブロック — 半角スペース 4 つ、コロンでヘッダ終了
if/elif/else、三項a if cond else bisはオブジェクト同一性、==は値の比較、Noneチェックはis Nonewhileのbreak/continue/elsefor inでイテラブルを巡回 —range、enumerate、zipmatch-case— 単純分岐、OR パターン、構造分解、dict パターン、ガード、クラスマッチング- 空のブロックには
pass
次回 (#4 コレクションと内包表記) では、最もよく使う四つのデータ構造 — list、tuple、dict、set と、それらを一行で作る 内包表記 を扱います。