Python:時間計測、再試行、キャッシュ、引数検証のためのデコレータ

Python:時間計測、再試行、キャッシュ、引数検証のためのデコレータ
1 分の読書

Pythonのデコレータは、関数の内部コードを変更することなく、実行時間の計測、再試行、キャッシュ、引数の検証といった共通の振る舞いを追加することを可能にします。Machine Learning Masteryで公開されたチュートリアルでは、著者がその仕組みを詳述し、例外を隠蔽することなく信頼性の高いデコレータを構築する方法を示しています。

出発点は、3つの関数からなるデータ処理モジュールで、時間の経過とともに付随的なタスクが追加されていきました。チュートリアルによると、ある関数は埋め込みデータを読み込む前にパスの存在を確認し、別の関数はtime.sleep()を用いたループ内でリクエストを繰り返し、3番目の関数は実行時間を計測し、結果を辞書に保存します。この手動で追加された境界コードは関数ごとに異なり、新しい関数を追加するたびにそのバリエーションが導入されるリスクがあります。

デコレータ、単純な代入

デコレータは、関数を引数として受け取り、ラッパー(元の呼び出しの前後にコードを実行するエンベロープ)を返す関数です。チュートリアルは、構文@が関数名の再代入と等価であること(Python用語集では糖衣構文と呼ばれる)を説明しています。したがって、デコレータは関数が定義された時点で一度だけ実行されます。

ラッパーはクロージャ(内側の関数が外側の関数の変数にアクセスを保持する仕組み)によって元の関数を保持します。パラメータ*argsと**kwargsは引数を渡し、結果はそのまま返されます。しかし、追加の対策を講じないと、ラッパーは関数の名前とドキュメントを置き換えてしまいます。そのためチュートリアルでは、functools.wrapsを使用してこれらのメタデータをコピーし、またテスト時に非デコレート化された関数にアクセスできるようにする属性__wrapped__をコピーすることを推奨しています。

四つの境界振る舞い

時間計測デコレータは、日付ではなく期間の計算に使用されるクロックであるtime.perf_counter()に依存しています。try/finallyブロック内に配置することで、関数が失敗した場合でも実行時間を表示し、例外は呼び出し元にそのまま伝播します。

再試行デコレータはファクトリであり、max_attempts、delay、再試行する例外などのオプションを受け入れます。プログラミングエラーがネットワーク問題として扱われないよう、設定された例外のみを再試行します。最後の試行では、単純なraiseで元の例外を再送出します。ただしチュートリアルは限界も指摘しています:このアプローチは、2回実行しても影響がない呼び出しに対してのみ安全です。支払い処理やメッセージ送信はこの基準を満たしません。

キャッシュについては、チュートリアルはfunctools.lru_cacheを推奨しており、これは最新の呼び出し結果をmaxsize(例えば256)の上限まで保持します。その制約は実装に由来します:引数はハッシュ可能でなければならず、変更可能なオブジェクトを返す関数は予期せぬ副作用を引き起こす可能性があります。最後の検証では、inspect.signature()を使用して、引数が位置引数やデフォルト値で渡された場合でもパラメータ名と関連付け、呼び出し前に単純なルールを適用します。

チュートリアルは慎重を期すためのルールで締めくくられています:デコレータは関数のビジネスロジックを決定すべきではありません。振る舞いがデータに依存する場合は、関数内、または検証の場合はデータモデル内に配置する方が良いでしょう。複数のデコレータを積み重ねる場合、適用順序が重要です。最も外側のラッパーが最上位に適用されるためです。

S’abonner
Notification pour
guest

0 Commentaires
Le plus ancien
Le plus récent Le plus populaire
0
Nous aimerions avoir votre avis, veuillez laisser un commentaire.x