かゆい所に手が届くswitch構文
別にswitch構文
がなくても、おそらくその処理は工夫次第で記述することが可能だろう。しかし、golang
やjavascript
に見られるswitch構文
を用いることで少なくとも可読性は上がるし、拡張性もif else
の組み合わせよりは良いはず。それにif else
の組み合わせで複雑な多条件を表現すると階層化されて条件がネストされるため視認性が下がるので、好きではない。switch構文
なら見かけ上ではcaseA
, caseB
, caseC
...は対等に見えるので視認性はこちらの方が良い(実際の内部処理は上から辿っている模様)
別にswitchがなくても処理は書ける
golang
func FizzBuzz(num int) string { if num % 15 == 0 { return "fizzbuzz" } else if num % 3 == 0 { return "fizz" } else if num % 5 == 0 { return "buzz" } return "no match" }
switch構文を使えば可読性と拡張性が増す
golang
func FizzBuzz(num int) string { // switchに渡す条件をtrueにしておけば条件式を記述出来る switch true { case num % 15 == 0: return "fizzbuzz" case num % 3 == 0: return "fizz" case num % 5 == 0: return "buzz" } return "no match" }
仮に「七の倍数の時はラッキーって出力しておいて〜」という仕様が急に決まったとしても以下のようにするだけで改修は終わる(ただし、評価順は上から順なので注意)
// 7の倍数という条件の方を優位にさせるため、 num % 3 == 0の上に記述(最初に21で被る) func FizzBuzz(num int) string { // switchに渡す条件をtrueにしておけば条件式を記述出来る switch true { case num % 15 == 0: return "fizzbuzz" case num % 7 == 0: return "ラッキー" case num % 3 == 0: return "fizz" case num % 5 == 0: return "buzz" } return "no match" }
「お、こんな便利な構文使わん理由ないやん、よっしゃPython
でも書いたろ」と思っても残念。Python
にはswitch構文
は実装されていない。理由は以下の公式ドキュメントのQAにて書かれている通り
if... elif... elif... else
の繰り返しで簡単に同じことができます。switch 文の構文に関する提案がいくつかありましたが、範囲判定をするべきか、あるいはどのようにするべきかについての合意は (まだ) 得られていません。
一言で言えば、他に書く方法あるから、それで何とかしてね。って感じかな。おっしゃる通りだけど、あってもいい気はする。で、このPython
にswitch構文
を導入するかどうかは2001年頃から議論されており、拒否されているよう。
www.python.org
それでもそれっぽいswitch構文が使いたい
です。なので、それっぽいのを記述する。すでに多くの先駆者がif else
使ったり、dictionary
にkeyをセットして関数をvalueに持たせたりと既出のものが多いので自分が調べた限り、この書き方は確認出来なかったので載せておく。正直な所はO(1)で高速にアクセス可能なdictionary
のkeyとvalueを使った複数条件処理を推したいところではある
if false: でネストさせるやつ
どういうこと。とりあえずコードを見せる
def fizzbuzz(num): """ num -> int return -> int """ if False: pass elif num % 15 == 0: print("fizzbuzz") elif num % 3 == 0: print("fizz") elif num % 5 == 0: print("buzz")
なぜif False:
と一番上の条件式に記述をしているかというと、先程話した通り、条件式が見た目上、ネストするのを回避するためと後の拡張性を確保したいからだ。処理としては一判定無駄になってしまうが、前者の理由を優先した。あとはswitch構文
同様にcase
と記述して条件式を書くもの、if
でswitch構文
っぽく記述するなら全てelif
という様に記述できるように意識した。正直、好みの問題だし下記の記述がdefaultだろう
def fizzbuzz(num): """ num -> int return -> None """ if num % 15 == 0: print(num, " is fizzbuzz") elif num % 3 == 0: print(num, " is fizz") elif num % 5 == 0: print(num, " is buzz")
無名関数のリストを作るやつ
無名関数を使ってこんなことが出来る
lst = [lambda x: x > 5, lambda x: x < 3] for i, func in enumerate(lst): print(i, "->", func(i))
実行結果
0 -> False # 0 > 5 1 -> True # 1 < 3
この様に無名関数をリストに保持させておくことで実行順序を保証できて、条件式を実行時評価状態にしておくことが出来るので、複雑な条件式を表現可能ということでswitch構文
っぽいものが記述出来る。dictionary
を使う場合にはkeyをa
, b
, c
のようにsortされても問題ないように順序を意識してkeyを作成する必要があるため、うーんとなり、この方法が思いついた
# 条件式を内包する無名関数を作成(実行時に評価される) # Falseを返しているのは実行時に評価式がFalse判定されたことを呼び出し元に伝えるため conditions = [ lambda x: "fizzbuzz" if x % 15 == 0 else False, lambda x: "fizz" if x % 3 == 0 else False, lambda x: "buzz" if x % 5 == 0 else False ] # 擬似switch構文(値と条件式を含む無名関数のリストを受け取る) def switch(val, judge_lst, default_res=None): """ # val -> any # judge_lst -> list[func] # default_res(None) -> any # return -> any """ # リストに含まれている関数を順に実行(リストなので) for func in judge_lst: res = func(val) # Flaseが返ってきていない(評価式がtrueとなった)ならbreak # この処理を削除すればフォールスルーになる(breakをしないと次の評価式に移るやつ) if res: return res return default_res
実行結果
for i in range(1, 31): print(i, " -> ", switch(i, conditions, "no match")) # 1 -> no match # 2 -> no match # 3 -> fizz # 4 -> no match # 5 -> buzz # 6 -> fizz # 7 -> no match # 8 -> no match # 9 -> fizz # 10 -> buzz # 11 -> no match # 12 -> fizz # 13 -> no match # 14 -> no match # 15 -> fizzbuzz # 16 -> no match # : # 30 -> fizzbuzz
今回はサンプルのために、ただ文字列を返すという無名関数を作成したが、無名関数内から定義済み関数をcallしたり、クラスのinstanceを返したりと割とやれることは多いはず。ただ、if else...
の実装に比べると明らかに重いので、上手く共通化出来る時ぐらいしか出番はないだろう
conditions = [ lambda x: str_slicer("fizzbuzz") if x % 15 == 0 else False, lambda x: str_slicer("fizz") if x % 3 == 0 else False, lambda x: str_slicer("buzz") if x % 5 == 0 else False ] def str_slicer(str_): """ str_ -> string return -> list[string] """ res = list() append = res.append for s in str_: append(s) return res
実行結果
for i in range(1, 31): print(i, " -> ", switch(i, conditions, "no match")) # 1 -> no match # 2 -> no match # 3 -> ['f', 'i', 'z', 'z'] # 4 -> no match # 5 -> ['b', 'u', 'z', 'z'] # 6 -> ['f', 'i', 'z', 'z'] # 7 -> no match # : # 29 -> no match # 30 -> ['f', 'i', 'z', 'z', 'b', 'u', 'z', 'z']
総評
すでにPython
でswitch構文
の代役に関する記事はそこそこあるが、個人的な興味と久しぶりにPython
を記述機会があったので遊んでみた。if else...
を淡々と書き続けていると頭がおかしくなりそうなので、最近は無名関数に条件式埋め込んでってことをよくやる。記事の主題とは関係ないが、Python
にswitch構文
がない理由を調べる中で、「デザインと歴史」というページにたどり着いて、少し読んでみたが面白かった。興味のある項目があったら読んでみるのも良いかもしれない