Pythonの三項演算子の代用について
今回は、Pythonでのショートコーディングで三項演算子に相当する処理をどう書くと短く書けるのかを考えていきます。
Pythonの三項演算子
z = x if b else y
三項演算子の例
z = x if n==10 else y
三項演算子なしで書く
Pythonではbool型はTruthy(True相当)の時に1、Falsy(False相当)で0として扱われるので、次のように書いたほうが短くてわかりやすいと思います。
z = [y,x][n==10]
そう考えるとPythonの前述のようにif elseで書く形は長ったらしいし、見づらいし、あまり嬉しくない文法であるように思います。
list構築時の副作用
しかし、listは構築時に評価(実行)されるため、次のようにx,yが関数である場合は、意図通りになりません。
z = [x(1),y(2)][n==10]
def x(i):
print("関数 x が呼び出されました")
return i*2
def y(i):
print("関数 y が呼び出されました")
return i*3
n=10
z = [x(1),y(2)][n==10]
実行結果
関数 x が呼び出されました
関数 y が呼び出されました
三項演算子は副作用がない
「じゃあ、やっぱり三項演算子で書いたほうがいいじゃん!!」と言われるかも知れません。三項演算子ならば、このような副作用はないですからね。
z = y(2) if n==10 else x(1)
副作用がある式はショートカット演算子で書こう
そこで皆んな大好き(?)ショートカット演算子の出番です。
ショートカット演算子についてご存知ない方は、こちらの記事をどうぞ。
論理演算子(&&, ||)の短絡評価
https://qiita.com/gyu-don/items/a0aed0f94b8b35c43290
ショートカット演算子を用いると、b and x or yのように書けます。
# bはbool型の変数であるか、具体的な条件式であるものとする。
z = b and x or y
この式は、bがTrueの時にxが評価され、bがFalseの時にyが評価されます。さきほどの三項演算子とほぼ同じ意味となります。
z = x if b else y
また嬉しいことに、bがTruthyの時には、xだけが評価され、yが評価されません。逆にbがFalsyの時にはyだけが評価されxは評価されません。三項演算子(if else)で書くより短く書けるので、ショートコーディングにおいては強い味方です。
ところが、bがTruthyでxがFalsyである場合、yも評価されてしまいます。
b=True
b and print("X") or print("Y")
実行結果
X
Y
なぜこのようになるかと言うと、print()は返し値を持たない関数で、すなわちNoneを返しているとみなせて、NoneはFalsyだから、or側のprint()も評価されてしまうわけですね。
XがFalsyになる場合のb and X or Y
XがFalsyになることがないなら単にb and X or Yと書けるのですが、そうでない場合は、もうひと工夫必要です。
Pythonでは空ではないシーケンスはTruthyであることを利用すると次のように書けます。
b=True
b and[print("X")]or print("Y") # Xだけが表示される
いい感じのアイデアで、このテクニックはショートコーディングで重宝しそうです。
左辺で変数に値を受け取る形のb and X or Y
次のようになるのですが、このままですと、bがTruthyの時にx(1)の返し値がlistに入ってやってきて困ります。
z = b and[x(1)]or y(2)
そこで、次のようにunpackを使ってlist剥がしを行うのがお勧めです。
z,*_ = b and[x(1)]or[y(2)]
unpackについて慣れていない方は、以下の記事を御覧ください。
Python の * 演算子 (iterable unpacking operator) の使い方
https://qiita.com/eumesy/items/dda85b70d28da61663cb
でも、上のようにするぐらいなら普通に三項演算子使ったほうが短いですね。
z =x(1)if b else y(2)
ショートカット演算子はifの代用になるか?
ショートコーディング的な観点で、ショートカット演算子が三項演算子(if else)の代用となりそうなところまでは見えてきましたが、普通のifの代用になるかは、色々難しい問題があります。
その一つが代入操作です。
代入を伴うb and X or Y
if b:x=1
else:y=2
上記のコードと同等のことをand orでやりたいとします。これはセイウチ演算子( := )を用いると可能です。
b and(x:=1)or(y:=2)
ところが、セイウチ演算子は、配列の添字つきの式に対しては使えないため次のようなものは不可能です。(今後のPythonでは改善される可能性もありますが、現時点で最新のPython 3.11においては出来ません。)
b and(x[0]:=1)or(y:=2)
添字つきの式へexecによる代入
そこで出てくるのがexec()です。これは文字列をPythonのプログラムとみなして実行してくれる大変ありがたい関数です。
b and exec("x[0]=1")or(y:=2)
しかし、exec()は返し値を持たないため、さきほどと同じ問題が生じます。そこでさきほど出てきたテクニックを用います。
b and[exec("x[0]=1")]or(y:=2)
これでかなりand or構文で出来ることの幅が広がりました。ifで書いた方が短いことも多々ありますが、面白いテクニックだと思うので紹介してみました。
まとめ
このb and[X]or Yの形は、私が発見したものです。(AtCoderで1000問程度のPythonのshortestの提出を見た限りはこのテクニックを使っている人はいなかったので)
良かったら(ショートコーディングに)活用してみてくださいね。
今後、ここでPythonでのショートコーディングのテクニックを網羅的かつ体系的に書いていこうと思いますので、良かったらフォローしてください。