Pythonの例外処理を使ってswitch文を実現する

これは [ちゅらデータ Advent Calendar 2022](https://qiita.com/advent-calendar/2022/churadata) の21日目の記事です。

# 前置き

結論だけ見たい人は前置きを読み飛ばして [#switch文の実現](#switch文の実現) までジャンプしてください。

## switch文

switch文とは、ある式の評価結果に応じて複数の処理に分岐させる記法で、C言語をはじめとした複数のプログラミング言語において利用することができます。

例えばC言語だとこのように記述します。

```c
switch (model_number) {
    case "HAC-001":
        printf("Nintendo Switch");
        break;
    case "SWX3220-16TMs":
        printf("YAMAHA Standard L3 Switch");
        break;
    case "WN5001H":
        printf("Panasonic Full Color Embedded Switch B");
        break;
    case "978-4-04-631767-4":
        printf("Switch! (1) Ikemen Jigoku Wa Mou Kamben");
        break;
    default:
        printf("Unknown Switch");
}
```

このような記法をPythonで実現する方法はいくつか存在しますが、ここでは例外処理を使った実現方法をご紹介します。

## 例外処理

例外というのは、PythonのExceptionのことです。そう、exceptでキャッチできるアレです。

```python
try:
    if whisky.stock <= 0 or soda.stock <= 0:
        raise Exception("out of stock")
    highball = whisky / soda
    self.consume(highball)
except Exception as e:
    print(f"Sorry... {e}.")
```

tryブロックの中で例外がraise（送出）されると、ただちにtryブロックを抜け、exceptブロックが実行されます。その際に発生した例外は、上記の例では変数eから参照することができ、どのような例外が発生したかを知ることができます。

「例外」と呼ばれている物の正体は、Exceptionクラスに代表される「例外クラス」という特別なクラスのインスタンスです。例外クラスは、Exceptionクラスを継承することによって自分で新しく定義することもできます。

```python
class WhiskyOutOfStockError(Exception):
    pass

class SodaOutOfStockError(Exception):
    pass
```

tryブロックの中で発生した例外のクラスによって、異なるexceptブロックに処理を分岐させることができます。

```python
try:
    if whisky.stock <= 0:
        raise WhiskyOutOfStockError()
    if soda.stock <= 0:
        raise SodaOutOfStockError()
    highball = whisky / soda
    self.consume(highball)
except WhiskyOutOfStockError:
    print(f"Sorry... Whisky is out of stock.")
except SodaOutOfStockError:
    print(f"Sorry... Soda is out of stock.")
except:
    print("Some unexpected error happened.")
```

exceptの後にキャッチしたい例外のクラスを書くことで、そのクラスが発生した場合にのみ実行されるブロックを作ることができます。このexceptブロックは複数書くことができ、どれにも当てはまらない例外が発生した場合には、一番下の例外クラス指定のないexceptブロックが実行されます。

## クラスの動的生成

ちなみに……あまり使う機会はないと思うのですが、クラス定義はプログラムの中で動的に生成することができます。例外クラスもクラスですから例外ではありません。クラスの動的生成にはtypesモジュールを使用します。

```python
import types

def make_new_outofstock_error(name):
    return types.new_class(name + "OutOfStockError", bases=(Exception,))

wine_outofstock_error = make_new_outofstock_error("Wine")
raise wine_outofstock_error()
```

上記では`WineOutOfStockError`という名前の例外クラスを作成し、その例外を送出しています。変数`wine_outofstock_error`には例外インスタンスではなくクラスが入っているため、raiseする際にかっこを付けてコンストラクタを呼び出し、インスタンス化しています。

# switch文の実現

前置きが長かったのですが、ここまでの知識を活用すると、switch文のような記法を実現することができるのです。

まずおまじないとして`SwitchCase`クラスを静的クラスとして定義し、`switch`関数と`case`関数を定義しておきます。

```python
class SwitchCase():
    _dict = dict()
    @staticmethod
    def get(x):
        import types
        if x not in SwitchCase._dict:
            SwitchCase._dict[x] = types.new_class("", bases=(Exception,))
        return SwitchCase._dict[x]
​
def switch(x):
    raise SwitchCase.get(x)()
​
def case(x):
    return SwitchCase.get(x)
```

`SwitchCase.get(x)`は、「xに紐付いた例外クラス」を返す関数です。紐付けの情報は辞書としてクラス変数に持っておきます。`switch(x)`は、xに紐付いた例外クラスを取得、インスタンス化し送出します。

これで準備は整いました。これを利用して実際にswitch文を書いてみましょう。

```python
print("Input the model number of your switch:")
model_number = input().strip()

try:
    switch(model_number)
except case("HAC-001"):
    print("Nintendo Switch")
except case("SWX3220-16TMs"):
    print("YAMAHA Standard L3 Switch")
except case("WN5001H"):
    print("Panasonic Full Color Embedded Switch B")
except case("978-4-04-631767-4"):
    print("Switch! (1) Ikemen Jigoku Wa Mou Kamben")
except:
    print("Unknown Switch")
```

いかがでしたか？ろくにテストしていませんが、Pythonで完ぺきに動作するswitch文が書けるようになったのではないかと思います。これで突然Pythonでswitch文を書きたくなったときも安心ですね。

　

　

　

　

　

ちなみにPython 3.10からは新機能として「**[構造的パターンマッチ](https://www.python.jp/news/wnpython310/index.html)**」がサポートされるようになったため、こんなあやしい方法を取る必要はまったくありません。構造的パターンマッチを使うと、上の例はこのように書くことができます。

```python
print("Input your model number of your switch:")
model_number = input().strip()

match model_number:
    case "HAC-001":
        print("Nintendo Switch")
    case "SWX3220-16TMs":
        print("YAMAHA Standard L3 Switch")
    case "WN5001H":
        print("Panasonic Full Color Embedded Switch B")
    case "978-4-04-631767-4":
        print("Switch! (1) Ikemen Jigoku Wa Mou Kamben")
    case _:
        print("Unknown Switch")
```

この他にももっと便利な使い方ができるようになっているため、新機能の構造的パターンマッチについてぜひチェックしてみてくださいね。