if文を書いていて、たった1つの変数に値を入れるだけなのに4行も使ってしまう。そんなもどかしさを感じたことはありませんか?
Pythonには、こうした分岐を1行で書ける条件式という文法があります。他の言語では三項演算子と呼ばれることが多く、Pythonでもこの呼び名で検索されることがよくあります。
便利な反面、使い方を間違えると一気に読みにくいコードになります。今回は条件式の基本から、実務でよく見かける落とし穴までを順番に見ていきましょう。
サンプルの実行結果は、手元のPython 3.11で確かめたものです。
条件式(三項演算子)とは何か¶
まずは、普通のif文で書いたコードを用意します。年齢に応じて、成人か未成年かを変数に入れる処理です。
age = 20
if age >= 20:
status = "成人"
else:
status = "未成年"
print(status) # 成人
これを条件式で書き直すと、次のように1行で済みます。
age = 20
status = "成人" if age >= 20 else "未成年"
print(status) # 成人
読み方は、条件が真なら左の値、偽なら右の値です。英語の文章のように、成人、もし20歳以上なら、そうでなければ未成年、と左から読めるのがPythonらしいところです。
Pythonの公式ドキュメントでは、この文法を条件式(conditional expression)と呼んでいます。値を3つ組み合わせる演算子なので三項演算子とも呼ばれますが、正式な名前は条件式だと覚えておくと調べものがしやすくなります。
文ではなく式であることがポイント¶
if文は文(statement)ですが、条件式は式(expression)です。式とは、計算すると1つの値になるもののことを指します。
値になるので、変数への代入だけでなく、関数の引数やreturnの後ろ、f文字列の中にもそのまま書けます。ここがif文との大きな違いです。
n = 3
print(f"{n}個のアイテム{'があります' if n > 0 else 'はありません'}")
# 3個のアイテムがあります
f文字列の書き方に自信がない方は、こちらの記事もあわせてどうぞ。
【関連記事】Pythonのf-stringとは?デバッグが驚くほど楽になる!
他の言語の?:とは順番が違う¶
JavaScriptやC言語を触ったことがある人は、条件 ? 真の値 : 偽の値 という書き方を見たことがあるかもしれません。Pythonの条件式は、これとは並び順が違います。
違いを表で見比べてみましょう。
| 言語 | 書き方 | 先頭に来るもの |
|---|---|---|
| Python | 真の値 if 条件 else 偽の値 | 真のときの値 |
| JavaScript・C言語・Java | 条件 ? 真の値 : 偽の値 | 条件 |
| Python(if文) | if 条件: ... else: ... | 条件 |
Pythonだけ、条件が真ん中に来ます。他の言語から来た人は、最初はこの順番に戸惑うはずです。
なぜこの順番になったのか¶
Pythonに条件式が入ったのは、PEP 308という提案がきっかけです。PEPとは、Pythonへの新機能や変更を提案する公式の文書のことです。
この提案では書き方の候補がいくつも出され、議論はなかなかまとまりませんでした。最終的には、2005年にPythonの生みの親であるGuido van Rossum氏が、X if C else Yの形にすると判断し、Python 2.5で導入されました。
PEP 308には、それまでandとorを組み合わせて条件分岐を書く方法が使われていて、それがバグを生みやすかったことも動機として書かれています。このandとorの話は、あとで詳しく扱います。
PEPそのものについては、こちらの記事で解説しています。
【関連記事】PythonのPEPとは何か?Pythonの進化の舞台裏について
条件式は片方しか評価されない¶
条件式には、知っておくと得をする性質があります。それは、条件が真なら左側だけ、偽なら右側だけが計算されるという点です。
公式ドキュメントにも、まず条件が評価され、その結果に応じてどちらか一方だけが評価されると書かれています。実際に、呼ばれると文字を表示する関数で確かめてみましょう。
def heavy():
print("heavy() が呼ばれた")
return 100
value = heavy() if False else 0
print(value) # 0(heavy() は呼ばれない)
実行しても、heavy() が呼ばれたという表示は出ません。条件がFalseなので、左側のheavy()はそもそも実行されていないのです。
この性質のおかげで、時間のかかる処理や、条件を満たさないとエラーになる処理を安全に書けます。たとえば、リストが空でないときだけ先頭の要素を取り出す、といった書き方ができますね。
items = []
first = items[0] if items else None
print(first) # None
items[0]は空のリストに対して実行するとIndexErrorになります。ですが、条件式なら空のときは右側のNoneだけが評価されるので、エラーは起きません。
ちなみに、if itemsという書き方は、空のリストが偽として扱われる性質を利用しています。どんな値が真と偽になるのかは、こちらで整理しています。
【関連記事】Pythonの真偽値を深く知る!if文で意外とハマるNoneや空リストの挙動を解説
リスト内包表記の中で使う¶
条件式がいちばん活躍する場面のひとつが、リスト内包表記です。ただし、ここには初心者が必ずと言っていいほど混乱するポイントがあります。
それは、ifを書く位置によって意味がまったく変わることです。次の2つを見比べてください。
nums = [3, -1, 0, 5, -2]
# 条件式:すべての要素を残し、値だけを変える
print([n if n > 0 else 0 for n in nums]) # [3, 0, 0, 5, 0]
# フィルタ:条件に合う要素だけを残す
print([n for n in nums if n > 0]) # [3, 5]
forの前に置いたif〜elseは条件式で、要素の数は変わりません。マイナスの値を0に置き換えるような、値の変換に使います。
一方で、forの後ろに置いたifは絞り込みです。条件に合わない要素はリストから取り除かれます。
forの後ろにelseは書けない¶
混乱しやすいのが、絞り込みのifにelseを付けてしまうケースです。次のように書くと、SyntaxErrorになります。
nums = [3, -1, 0]
print([n for n in nums if n > 0 else 0]) # SyntaxError: invalid syntax
値を置き換えたいならelse付きでforの前に、要素を減らしたいならelseなしでforの後ろに書く、と覚えておきましょう。内包表記そのものの基本は、こちらの記事で解説しています。
【関連記事】Pythonのリスト内包表記を使いこなせ!3行のループを1行にまとめる書き方
条件式の落とし穴¶
ここからは、実務でよく見かける条件式の失敗パターンを紹介します。どれもエラーにならずに動いてしまうので、気づきにくいのが厄介です。
入れ子にすると一気に読めなくなる¶
条件式は、else の後ろにさらに条件式を続けることができます。点数からランクを決める処理を書いてみましょう。
score = 72
rank = "A" if score >= 80 else "B" if score >= 60 else "C"
print(rank) # B
動きとしては正しく、80点以上ならA、60点以上ならB、それ以外はCになります。ですが、パッと見て意味がわかったでしょうか?
条件が3つ、4つと増えると、どこまでが1つの分岐なのか追えなくなります。こういう場合は、素直にif〜elif〜elseで書いたほうがずっと読みやすくなります。
私は10年ほどエンジニアとして開発に関わってきましたが、コードレビューで差し戻しが多いのが、この入れ子の条件式です。書いた本人は1行で済んで満足していても、半年後に読み返した本人ですら意味を取り違えていたことがありました。
条件式を使うのは、分岐が1つで、両方の値が短いときだけ。これを自分のルールにしておくと失敗が減ります。
orで代用すると0や空文字で壊れる¶
値がないときに初期値を入れたい場面で、orを使った書き方をよく見かけます。orは左側が偽のとき右側を返すので、一見すると条件式の代わりになりそうです。
ですが、ここに罠があります。0や空文字も偽として扱われるため、本当は正しい値なのに初期値で上書きされてしまうのです。
count = 0 # 0件という正しい値
print(count or 10) # 10(0が消えてしまう)
print(10 if count is None else count) # 0(意図どおり)
件数が0件というのは、立派な正しいデータです。それをorで判定すると、0が10に化けてしまいます。
値が入っていないことをNoneで表しているなら、is Noneで判定する条件式を使いましょう。こうすれば、0や空文字はそのまま残ります。
and と or の組み合わせは条件式の代わりにならない¶
PEP 308以前によく使われていたのが、条件 and 真の値 or 偽の値 という書き方です。先ほど触れた、バグを生みやすかった書き方がこれです。
この書き方は、真の値そのものが偽として扱われる値のときに壊れます。次の例では、条件はTrueなのに右側の値が返ってきます。
flag = True
print(flag and "" or "デフォルト") # デフォルト(意図とちがう)
print("" if flag else "デフォルト") # (空文字が返る)
flag and "" の結果は空文字で、空文字は偽です。そのためorの右側が選ばれてしまいました。
古いコードやネット上のサンプルでこの書き方を見つけたら、条件式に書き換えるのが安全です。
優先順位が低いので範囲を勘違いしやすい¶
条件式は、Pythonの演算子の中でも優先順位がとても低い部類に入ります。そのため、足し算などと組み合わせると、思っていたのと違う範囲が条件式になることがあります。
会員なら送料500円を足す、というつもりで書いた次のコードを見てください。
is_member = False
total = 1000 + 500 if is_member else 0
print(total) # 0(1000円が消えてしまう)
total = 1000 + (500 if is_member else 0)
print(total) # 1000
1行目の書き方は、1000 + 500全体が条件式の左側として扱われます。そのため、会員でないときは合計が丸ごと0になってしまいました。
条件式を計算の一部として使うときは、かっこで範囲をはっきりさせるのが鉄則です。読む人に優しいだけでなく、バグも防げます。
条件式とif文の使い分け¶
ここまで見てきたように、条件式は便利ですが万能ではありません。どちらを選べばいいか迷ったときの目安を、表にまとめておきます。
| 場面 | おすすめ | 理由 |
|---|---|---|
| 2つの値のどちらかを代入する | 条件式 | 1行で意図が伝わる |
| 内包表記やf文字列の中で値を切り替える | 条件式 | if文は書けない場所 |
| 分岐が3つ以上ある | if〜elif〜else | 入れ子の条件式は読みにくい |
| 分岐ごとに複数の処理をする | if文 | 条件式は値を1つ返すだけ |
| 処理を途中で抜けたい | if文とreturn | 早期リターンで見通しが良くなる |
迷ったときは、声に出して読んでみるのがおすすめです。1回で意味が伝わるなら条件式、言い直したくなるならif文、という判断で大きく外れることはありません。
条件式ではなく関数で書ける場合もある¶
条件式を書く前に、もっとシンプルな方法がないかも考えてみましょう。たとえば、マイナスなら0にするという処理は、max()を使えば条件式なしで書けます。
n = -5
print(n if n > 0 else 0) # 0
print(max(n, 0)) # 0
どちらも結果は同じですが、max(n, 0)は0未満にしないという意図がひと目で伝わります。標準の関数で表現できるなら、そちらを優先するのもひとつの手です。
if文のネストを減らす早期リターンについては、こちらの記事で詳しく解説しています。
【関連記事】早期リターンでネストを浅く! 読みづらいif文をスッキリさせるリファクタリング手法
よくある疑問¶
最後に、条件式について初心者の方からよく聞かれる疑問をまとめておきます。
まず、elseは省略できるのかという疑問です。答えはノーで、条件式ではelseが必須です。値を必ず1つ返す式なので、偽のときの値も決めておかなければなりません。
次に、条件式はif文より速いのかという疑問もよくあります。どちらも条件を1回評価して片方を選ぶだけなので、速さを理由に選ぶものではありません。読みやすさで選びましょう。
最後に、lambdaの中で分岐したいときはどうするのかという疑問です。lambdaの中には文が書けないため、if文は使えません。こういう場面こそ条件式の出番です。
label = lambda n: "偶数" if n % 2 == 0 else "奇数"
print(label(4)) # 偶数
print(label(7)) # 奇数
まとめ¶
Pythonの条件式は、真の値 if 条件 else 偽の値という形で、分岐を1行の式として書ける文法です。他の言語の?:とは並び順が違い、条件が真ん中に来ることを覚えておきましょう。
条件式は片方しか評価されないので、空のリストの先頭を取り出すような処理も安全に書けます。内包表記の中では、forの前に置くと値の変換、forの後ろに置くと絞り込みになるという違いにも注意が必要です。
一方で、入れ子にしたり、orやandで代用したりすると、エラーにならないまま意図と違う動きをします。分岐が1つで、短く書けるときだけ使うというルールを守れば、条件式は心強い味方になってくれます。
まずは、手元のコードで4行のif文を1つ見つけて、条件式に書き換えてみてください。そして読みやすくなったかどうかを、自分の目で確かめてみましょう。
ここまでお読みいただきありがとうございました。