2018/08/31

Python Tips: switch 文を使いたい

Python に似た言語にはよくあって Python に無いもののひとつに「 switch 文」があります。今回は switch 文を持たない Python において switch 文を書きたくなったときの代わりの書き方 をご紹介したいと思います。

おそらく Pythonista の多くが使っているのは次の 2 つの方法のどちらかです。

  • switch 文の代わり 1: ifelif
  • switch 文の代わり 2: dict

switch 文の代わり 1: ifelif


第一の方法は単純に ifelif 構文を使うというものです。

def printer_factory(name):
    if name == 'json':
        return JsonPrinter()
    elif name == 'yaml':
        return YamlPrinter()
    elif name == 'csv':
        return CsvPrinter()
    else:
        raise ValueError('Invalid printer: {}'.format(name))

この方法だと name == の部分を分岐の数だけ繰り返す必要がありますが、キーワード elif が短いおかげでわりとシンプルに書くことができます。

最後の else のところに来たときに例外をあげるかフォールバック値を返すかはそのときどきで適切な方を選ぶとよいでしょう。

switch 文の代わり 2: dict


もうひとつの代表的なアプローチは dict を使った方法です。

def printer_factory_改(name):
    printer_map = {
        'json': JsonPrinter,
        'yaml': YamlPrinter,
        'csv': CsvPrinter,
        'xml': XmlPrinter,
        'html': HtmlPrinter,
        'mild': MildPrinter,
        'wild': WildPrinter,
    }

    try:
        return printer_map[name]()
    except KeyError as e:
        raise ValueError('Invalid printer: {}'.format(name))

分岐を表すマップをあらかじめ定義しておき、辞書のキールックアップを使って分岐させます。

指定された値が存在しなかったとき(= switch 文で default に来たときに相当)の挙動として、例外をあげたいのであれば KeyError をキャッチして適切な例外をあげ直せば OK です。フォールバック値を返したければブラケット( [] ではなく get() メソッドを使ってデフォルト値を設定しながら値を返すとよいでしょう。

# 該当するものが見つからなかった場合はフォールバック値を返す
default_value = HtmlPrinter
return printer_map.get(name, default_value)()

こちらの方法で気をつけるべき点は、マップを作成するときに値を評価してしまわないことです。例えば上の printer_factory_改 は次のように書くこともできますが、こうするとマップを用意しているときにすべての Printer クラスのインスタンスが生成されてしまうのであまりよくありません。

def printer_factory_改悪(name):
    printer_map = {
        'json': JsonPrinter(),
        'yaml': YamlPrinter(),
        'csv': CsvPrinter(),
        'xml': XmlPrinter(),
        'html': HtmlPrinter(),
        'mild': MildPrinter(),
        'wild': WildPrinter(),
    }

    try:
        return printer_map[name]
    except KeyError as e:
        raise ValueError('Invalid printer: {}'.format(name))

Python ではクラスそのものもオブジェクトでありクラスを dict の値として格納することができるので、ファクトリクラスはできるだけ printer_factory_改悪 よりも printer_factory_改 の形で書くのがよいでしょう。

以上です。

ifelifdict のどちらを使うべきかはそのときの分岐の数や周辺のコード、コーディングルール等によって変わってくると思うので、そのときどきでより適切な方を選ぶとよいでしょう。

アーキテクチャの良し悪しの観点からいえば、 switch 文を使うべき場面は非常にかぎられてくるはずなので、 switch 文が無いということは Python らしいいい制約、なのかもしれません。

というわけで、 Python における「 switch 文の代わりの書き方」についてでした。

参考

Python 公式のドキュメントの FAQ に「なぜ Python には switch case が無いの?」という項目があるので、経緯等に興味がある方はそちらもご覧になってみるとよいかと思います。

0 件のコメント: