
if / for / while、実務ではどれから直せば読みやすくなる?
分岐がぐちゃぐちゃ、ループが遅い、while True が止まらない…その悩み、今日で終わらせましょう。
この記事は、pythonbunseki.comの定番トーンで「制御構文の設計」を実務目線で解説します。ポイントは、(1) 条件は名詞化して早期return、(2) ループはデータの“形”から設計(enumerate
/zip
/内包表記)、(3) 終了条件は先に置く(break/continue/return
の“意図の強さ”で使い分け)。
この記事で身に付くこと
- 読める条件分岐:条件を名詞化して早期returnに落とす型
- 速いループ設計:イテラブルに合わせた
for
・内包表記の使い分け - 止まる処理:
while
で終了条件を先に置く・ガード節の付け方
よくあるつまずきは「読めない・遅い・止まらない」
レビューで最も指摘が入るのはこの3つ。いずれも“設計”で解消できます。
- 条件が読めない:
if a and not b or c == 3
のように意図が見えない。 - 反復が遅い:ループ内で連結や検索を繰り返して
O(n^2)
になる。 - 終了条件が曖昧:
while True
のまま無限ループ、break
が乱立。
解決の順番は「条件に名前」→「データの形で回す」→「終了条件は先に置く」。この型に沿って整理していきます。
現場メモ(ふみと)
データ/マーケティングサイエンティストとして10年、レビューで「読める」と感じるコードは決まって、(1) 条件が名詞(is_valid_user
など)、(2) ループはデータ駆動(for user in users
のように名前が語る)、(3) 早期returnでネストが浅い。この3点でした。
if:条件は名詞化&早期returnで浅くする
複雑な式をそのままifに書かないのがコツ。意図を「名前」に切り出してから判定しましょう。
悪い例
def discount(price, member, coupon):
if price > 10000 and (member == "gold" or coupon == "VIP") and not (price % 2 == 1):
return int(price * 0.9)
else:
return price
良い例(意図を名前に)
def discount(price, member, coupon):
is_large = price > 10000
is_special = member == "gold" or coupon == "VIP"
is_even_price = price % 2 == 0
if not (is_large and is_special and is_even_price):
return price # 早期returnでネストを浅く
return int(price * 0.9)
- 短絡評価:
A and B
は AがFalseならBを評価しない。副作用のある式は入れない。 - 連鎖比較:
0 < x < 10
は読みやすい。 - 空の真偽値:
[]
/""
/0
/None
は False 相当。if not items:
が定番。
for:イテラブルの「形」から設計する
手元のデータ構造に合わせて書けば、意味が伝わるコードになります。
users = [{"id": 1, "name": "A"}, {"id": 2, "name": "B"}]
for user in users: # 変数名は意味が伝わるように
print(user["name"])
for i, user in enumerate(users, start=1):
print(i, user["name"])
names = ["A", "B", "C"]
ages = [23, 31, 27]
for name, age in zip(names, ages):
print(name, age)
for key, value in mapping.items():
...
内包表記は「式1つ・副作用なし」の時だけが基本です。
# 良い:式が1つで副作用なし
squares = [x * x for x in range(10) if x % 2 == 0]
計算量の目安(ざっくり)
- ネスト1段:O(n)、2段:O(n^2)。
- まず先に絞る(条件でfilter)→必要な要素だけ回す。
while:終了条件は先に決める(可能ならfor優先)
外部要因で終わる処理(入出力・逐次取得など)だけ while
を選び、ガード節(最大回数・タイムアウト)を用意します。
def read_until_empty(lines):
result = []
for line in lines: # 可能ならforでイテラブルを回す
if not line.strip():
break
result.append(line)
return result
while True:
line = input(": ")
if not line:
break # 明示的に終了
process(line)
for-else / while-else:breakしなかった時の後処理
if-else
のelse
とは意味が違います。ループをbreak
せず正常終了した時だけ実行されます。
# 例:素数判定
def is_prime(n: int) -> bool:
if n < 2:
return False
for p in range(2, int(n ** 0.5) + 1):
if n % p == 0:
return False # 見つかったら即終了
else:
return True
break / continue / return の意図の強さ
- return:関数全体を終了(最も強い)
- break:外側の1ループを終了
- continue:今回の1回だけスキップ
多用して読みにくければ、条件を名詞化して早期returnに寄せましょう。
テンプレ集:集計・検索・変換
A) 集計(group)
from collections import Counter, defaultdict
# 1. 件数カウント
c = Counter(event\["type"] for event in events)
# 2. 合計/平均(辞書で蓄積)
sums, counts = defaultdict(int), defaultdict(int)
for e in events:
k = e\["type"]
sums\[k] += e\["value"]
counts\[k] += 1
avg = {k: sums\[k] / counts\[k] for k in sums}
B) 検索(find)
# 条件を関数に名詞化
def is_target(u):
return u["active"] and u["score"] >= 80
# 1. 最初の1件
found = None
for u in users:
if is\_target(u):
found = u
break
# 2. なければデフォルト
result = found or {"id": -1, "name": "N/A"}
C) 変換(map / filter)
# 1. シンプル → 内包表記
names = [u["name"].strip().title() for u in users if u["active"]]
# 2. 複雑 → for文に戻す
cleaned = []
for u in users:
if not u["active"]:
continue
name = u["name"].strip().title()
cleaned.append({**u, "name": name})
用途別ミニレシピ(最小構成)
- データ整形:
for x in rows
→if 条件
で先に絞る → 副作用がなければ内包表記。 - 検索/検知:見つからない時の処理は
for-else
or 早期return。 - 対話/監視:
while True
+ガード節(最大回数/タイムアウト)。 - 業務スクリプト:条件は名詞化 → 早期return → ログ出力。→ [内部リンク:例外処理とログ設計]
ハンズオン(15分×6問)
Q1:偶数だけ2乗(内包表記)
nums = [1,2,3,4,5,6]
# TODO: 偶数だけ2乗したリストを作る
[x*x for x in nums if x % 2 == 0]
Q2:最初の80点以上のユーザ(for-else)
users = [{"name":"A","score":70},{"name":"B","score":85}]
# TODO: 80点以上が見つかったらname、なければ"N/A"
for u in users:
if u["score"] >= 80:
res = u["name"]
break
else:
res = "N/A"
Q3:入力が空なら終了(while)
# TODO: 入力を受け取り、空行で終了して件数を表示
cnt = 0
while True:
line = input(": ")
if not line:
break
cnt += 1
print(cnt)
Q4:ネストを浅く(早期return)
def is_valid(age, email):
# TODO: 年齢が18以上、メールに"@"を含む
def is_valid(age, email):
if age < 18:
return False
if "@" not in email:
return False
return True
Q5:enumerateでインデックス表示
items = ["a","b","c"]
# TODO: 1始まりで "1:a" の形で出力
for i, x in enumerate(items, start=1):
print(f"{i}:{x}")
Q6:条件名詞化の練習
user = {"age":25, "role":"admin", "active":True}
is_admin = user["role"] == "admin"
is_active = user["active"]
if is_admin and is_active:
...
アンチパターン → 一発で直す
1) フラグだらけの while True
# NG
while True:
x = get()
if x is None:
break
if x == "ERR":
handle()
continue
if len(x) > 100:
process(x)
# → OK(ガード節+関数化)
def should_process(x):
return x and x != "ERR" and len(x) > 100
for x in iter(get, None): # get() が None を返すまで
if not should\_process(x):
continue
process(x)
2) ループ内での逐次 +
連結
# NG(O(n^2))
s = ""
for x in items:
s += x
# → OK(O(n))
s = "".join(items)
3) range(len(seq))
で回す
# NG
for i in range(len(seq)):
print(seq[i])
# → OK
for x in seq:
print(x)
4) 多段ネスト
# NG
for a in A:
for b in B:
if cond(a, b):
do(a, b)
# → OK(早期continue / 先に絞る)
for a in A:
if not cond_a(a):
continue
for b in (x for x in B if cond_b(x)):
if cond(a, b):
do(a, b)
ちょい上級:代入式(ウォルラス :=
)の安全な使い方
「同じ計算を2回書かない」ための省略です。読みづらくなるなら使わない判断も正解。
import re
# パースに成功した行だけ処理
while (line := input(": ")):
if (m := re.match(r"^(\d+),(\w+)$", line)):
num, name = int(m.group(1)), m.group(2)
handle(num, name)
テストしやすい制御構文(pytest)
分岐・ループを純粋関数に寄せるとテストが楽になります。最小例:
def pick_actives(users):
return [u for u in users if u.get("active")]
def test\_pick\_actives():
users = \[{"active": True}, {"active": False}]
assert pick\_actives(users) == \[{"active": True}]
→ [内部リンク:単体テストpytest入門]
ここまでのまとめ
- 条件は名詞化して早期return。
- 反復はイテラブル設計(
enumerate
/zip
/内包表記)。 - 終了条件は先置き、ガード節を忘れない。
今日やること(30〜45分)
- 条件の名詞化:既存スクリプトの
if
を1カ所、名前付きブールに置換。 - 内包表記:式1つのループを1本、内包表記に変換。
- for-else:見つからなかった時の処理を
else
へ。 - pytest:分岐ロジックを1本テスト化。→ [内部リンク:単体テストpytest入門]
関連記事(サイト内)
-
-
Python関数とスコープの設計術:I/O分離×型ヒントで再利用性とテスト容易性を最大化
関数がどんどん太ってテストしづらい…どう整理すればいい? globalやprintが混ざっていて、ユニットテストを書く気力が出ない… この記事では、関数とスコープの設計を“純度×境界×型”の視点で整え ...
-
-
Python実務の型:例外処理と構造化ログでエラーに強いコードを書く
例外処理って、結局どこまでやれば“実務で困らない”の? ログも整えるのって大変そう…最低限の型、ください! この記事は、pythonbunseki.comの実務トーンで「防ぐ→気づく→復旧する」をコー ...
-
-
Python標準ライブラリ珠玉の10選|datetime・pathlib・itertoolsで実務が回るチートシート
外部ライブラリを増やさずに、コードの品質と保守性をグッと上げたい…。 標準ライブラリ“だけ”で、どこまで実務が回せる? 今回はそんな悩みを解決するために、Python標準ライブラリの“珠玉の10選”を ...
-
-
【コピペOK】pytestで“壊れないPython”を作る12ステップ
「昨日は動いてたのに、今日は壊れた…」 データ分析やETL、機械学習のコードで多発するこの悲劇。実は“テスト不在”が9割です。 本記事は、pytestで“壊れないPython”を作るための実務ガイド。 ...
TechAcademy データサイエンスコース(受講料:174,600円~ ※更に割引あり)

株式会社キカガク AI人材長期育成コース(受講料:237,600円~)

最近のコメント