当サイトは、アフィリエイト広告を利用しています
pythonのデコレーターの使い方とつかいどころ
をまとめておく。
pythonのデコレーターは既存の関数やメソッドをラップすることで
その振る舞いを拡張、または変更することができる機能のこと。
デコレーターを使うことで元の関数のソースを変更せずに
機能を追加したり、共通の処理をまとめたりできる。
シンプルなデコレーターを使って使い方をまとめる。
デコレーターを使う手順は
になる。
この手順でより対象の関数はデコレーターでラップされているので
関数実行時、デコレーター処理が適用された状態で実行されるようになる。
デコレーター関数を作成する。
def my_decorator(func):def wrapper():print("デコレーターの前処理")func()print("デコレーターの後処理")return wrapper
引数funcをラップしてデコレーター処理としてprint出力を行う関数。
funcにはデコレーター処理を適用したい関数を渡すようにする。
デコレーター処理を適用したい関数にデコレーターを付与する
def my_decorator(func):def wrapper():print("前処理")func()print("後処理")return wrapper@my_decoratordef say_hello():print("こんにちは!")def main():say_hello()if __name__ == "__main__":main()# 実行結果# 前処理# こんにちは!# 後処理
say_hello()関数にデコレーター「@my_decorator」を付与する。
デコレーターはデコレーター関数名になる。
実行時の動きとしては下記のようになる。
引数を持つ関数にもデコレーターを適用することができる。
別に引数がなくてもいいので基本がこっちで作ったほうが拡張性は高くなる
def my_decorator(func):def wrapper(*args, **kwargs):print("デコレーターの前処理")result = func(*args, **kwargs)print("デコレーターの後処理")return resultreturn wrapper@my_decoratordef say_hello(name):print(f"こんにちは、{name}さん!")def main():say_hello(name = "tom")if __name__ == "__main__":main()
(*args, **kwargs)にすることで任意の数の位置引数とキーワード引数の両方を受け取ることができる。
※要はなんでも受けれる
下記のようにすれば引数の値を取得することも可能
def my_decorator(func):def wrapper(*args, **kwargs):# キーワード引数から 'name' を取得name_value_key = kwargs.get('name')if not name_value_key and args:name_value_arg = args[0] # 位置引数として取得print(f'位置引数:{name_value_arg}')else:print(f'キーワード引数:{name_value_key}')result = func(*args, **kwargs)print("デコレーターの後処理")return resultreturn wrapper@my_decoratordef say_hello(name):print(f"こんにちは、{name}さん!")def main():say_hello(name = "tom") # キーワード引数で実行say_hello("john") # 位置引数で実行if __name__ == "__main__":main()# 実行結果# キーワード引数:tom# こんにちは、tomさん!# デコレーターの後処理# 位置引数:john# こんにちは、johnさん!# デコレーターの後処理
デコレーターの使いどころとしていくつかサンプルを
実装する。
実行する関数をログに出したい場合は
関数名をログに出力するデコレーターを作成すれば
無駄なコードを書かなくて済む
import logginglogger = logging.getLogger(__name__)logger.setLevel(logging.DEBUG)console_handler = logging.StreamHandler()console_handler.setLevel(logging.INFO)formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')console_handler.setFormatter(formatter)logger.addHandler(console_handler)def my_decorator(func):def wrapper(*args, **kwargs):logger.info(f"関数 {func.__name__} が実行されました")result = func(*args, **kwargs)return resultreturn wrapper@my_decoratordef say_hello(name):print(f"こんにちは、{name}さん!")@my_decoratordef say_goodbye(name):print(f"さよなら、{name}さん!")def main():say_hello(name = "tom")say_goodbye(name = "tom")if __name__ == "__main__":main()# 実行結果# 2024-08-27 21:38:39,140 - INFO - 関数 say_hello が実行されました# こんにちは、tomさん!# 2024-08-27 21:38:39,141 - INFO - 関数 say_goodbye が実行されました# さよなら、tomさん!
下記のFlaskで作ったREST APIPIではアクセスログを出力用デコレーターを作成して使用している
※access_logのところらへんまたpythonのloggingについては下記で詳しくまとめています
同じ処理のエラーハンドリング(try~exept)をデコレーターにすることで
無駄な実装を減らせる。
# dbアクセスエラー用ラッパーdef pymongo_error_wrapper(func):def wrapper(self, collection_name, *args, **kwargs):try:return func(self, collection_name, *args, **kwargs)except PyMongoError as e:e.detail = f"Failed to find documents in database '{self.db_name}', collection '{collection_name}'"raise eexcept Exception as e:logger.error(f"An unexpected error occurred: {str(e)}")raisereturn wrapper
引数の関数(MongoDBアクセス処理)をtry~exeptでラップするデコレーターを作る
MongoDBアクセス処理関数にデコレーターをつける
from app.database import Databasefrom app.error_handling import pymongo_error_wrapperclass DatabaseModel:# dbごとにインスタンスを作成def __init__(self, db_name):self.db = Database.client[db_name]self.db_name = db_name# 全件取得@pymongo_error_wrapperdef find_documents(self, collection_name, query={}, projection={'_id': 0}):collection = self.db[collection_name]return list(collection.find(query, projection))# 指定取得@pymongo_error_wrapperdef find_document_by_id(self, collection_name, document_id, projection={'_id': 0}):collection = self.db[collection_name]return collection.find_one({"user_id": document_id}, projection)# 登録@pymongo_error_wrapperdef insert_document(self, collection_name, document):collection = self.db[collection_name]return collection.insert_one(document)# 削除@pymongo_error_wrapperdef delete_document(self, collection_name, document_id):collection = self.db[collection_name]return collection.delete_one({"user_id": document_id})# 更新@pymongo_error_wrapperdef update_document(self, collection_name, document_id, update_data):collection = self.db[collection_name]return collection.update_one({"user_id": document_id},{"$set": update_data})
上記二つは
より抜粋Pythonのデコレーターを使うことで共通処理や関数の拡張を
関数自体をいじらずにできるのでかなり便利だと感じた。
今後は積極的に取り入れていきたい。