デコレータで例外処理を共通化する[Python]

業務でSaas機械学習サービスを開発しているのですが、そこである決済サービスを使用しています。

その決済サービスのライブラリ(Python)を利用していると、ライブラリで定義されたExceptionが返ってくるのですが、

ライブラリ定義のExceptionに対して、同じような例外処理を業務コードのあちこちに書かないといけなく、とても冗長でした。

そこでPythonデコレータを使って例外処理を共通化したので、今回はそのことについての記事を少し書きます。

決済サービスの話はマイナーケースだとは思いますが、サービス開発する上での例外処理を共通化したい場面はあると思うので参考になれば幸いです。

例外処理を共通化をしない場合

Pythonで普通に例外処理を書こうとするとこのようになると思います。

ログ処理やAPIでHttpExceptionを投げないといけない時などはかなり冗長になってしまいます。

例外処理だけでなくtryの中の処理もそれぞれ同じ処理内容だとメソッドを切って共通化するだけで終わりますが、

例外処理だけ共通化したい場合は工夫が必要です。

def pay(product: Product):
    try:
        ...
    except HogeHogeException as e
        ...
    except FugaFugaException as e
        ...
    except Exception as e:
        logging.critical(e)
    ...
    return ...


def subscribe_to(product: Product):
    try:
        ...
    except HogeHogeException as e
        ...
    except FugaFugaException as e
        ...
    except Exception as e:
        logging.critical(e)
    ...
    return ...

デコレータを使って例外処理を共通化した場合

デコレータを使うと上のコードを以下のように記述することができます。

例外処理を一箇所にまとめて切り出すことができました。例外処理に修正が入る場合も簡単に修正できそうです。

def payment_exception(func):
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except HogeHogeException as e
            ...
        except FugaFugaException as e
            ...
        except Exception as e:
            ...

    return wrapper


@payment_exception
def pay(product: Product):
    ...
    return ...


@payment_exception
def subscribe_to(product: Product):
    ...
    return ...

ちなみに、デコレータとは引数として関数を受け取り、別の関数を返す関数のことです。

デコレータはシンタックスシュガー@payment_exceptionと簡単に書くことができていますが

pay = payment_exception(pay)と動作的に等価です。

終わりに

今回はデコレータによる例外処理の共通化を行いましたが、例外処理だけでなく、ロギングなどいろいろな処理の共通化できるので試してみてください。