デコレータ:Pythonで最強の技術
Takashi Yamamoto
Infrastructure Engineer · Leapcell

Pythonデコレータの詳細な解説
I. デコレータとは
Pythonにおいて、デコレータは本質的にPythonの関数です。他の関数の元のコードを変更せずに、追加の機能を追加できる独自の機能を持っています。デコレータの戻り値も関数オブジェクトです。簡単に言うと、別の関数を返すように特別に設計された関数です。
デコレータは、アスペクト指向の要件が存在する多くのシナリオで重要な役割を果たします。例:
- ログの挿入:関数の実行プロセスと関連情報を記録するのに役立ち、デバッグとシステム監視に役立ちます。
- パフォーマンステスト:関数の実行時間を計算し、それによってそのパフォーマンスを評価できます。
- トランザクション処理:一連の操作がすべて成功するか、すべて失敗するかを保証し、データの整合性と一貫性を保証します。
- キャッシング:計算コストの高い関数については、計算結果をキャッシュします。次回同じ入力が発生した場合、キャッシュされた値を直接返し、効率を向上させます。
- 権限の検証:ユーザーが関数を実行する前に対応する権限を持っているかどうかを確認し、システムのセキュリティを確保します。
デコレータは、このような問題を解決するための優れた設計ソリューションを提供します。デコレータを使用することで、関数のコア機能とは無関係でありながら繰り返し現れる大量のコードを抽出し、高レベルのコード再利用を実現できます。
要約すると、デコレータのコア機能は、既存のオブジェクトに追加の機能を追加し、コード構造をより明確にし、機能をより豊富で柔軟にすることです。
II. デコレータが必要な理由
(I) 簡単な例
まず、簡単な関数を考えてみましょう。
def foo(): print('i am foo')
この関数は、文字列i am foo
を出力するだけです。
(II) 要件の追加
ここで、関数の実行ログを記録するという新しい要件があるとします。そこで、ログ関連のコードをコードに追加します。
def foo(): print('i am foo') print("foo is running")
この時点で、foo
関数は、元の機能に加えて、ログを出力する機能を追加しました。
(III) より多くの関数の要件
100個の関数があり、すべてそのようなログ記録の要件を追加する必要があり、将来的には、これらの100個の関数の実行前にログを出力するという要件を追加する必要があるかもしれないとします。関数コードを1つずつ変更すると、大量の重複コードが生成されます。これは明らかに良い解決策ではありません。
重複コードの記述を減らすために、ログ関連の操作を専門的に処理する関数を再定義できます。ログ処理が完了したら、実際のビジネスコードが実行されます。例を次に示します。
def use_logging(func): print("%s is running" % func.__name__) func() def bar(): print('i am bar') use_logging(bar)
実行結果は次のとおりです。
bar is running
i am bar
この例では、関数use_logging
はデコレータです。関数内で実際のビジネスメソッドを実行するfunc
をラップします。正式には、bar
関数はuse_logging
によってデコレートされているように見えます。関数が出入りするときのログ記録操作はアスペクトと呼ばれ、このプログラミング方法はアスペクト指向プログラミングと呼ばれます。
このuse_logging
関数を通じて、関数にロギング関数を追加することに成功しました。将来的には、ロギング関数を追加する必要がある関数がいくつあっても、またはログ形式を変更する必要がある場合でも、use_logging
関数を変更し、use_logging(デコレートされた関数)
を呼び出すだけで、目的の効果を実現できます。例:
def use_logging(func): print("%s is running" % func.__name__) return func @use_logging def bar(): print('i am bar') bar()
III. 基本的なデコレータの紹介
(I) デコレータのシンタックスシュガー
Pythonは、デコレータのシンタックスシュガーとして@
記号を提供しており、デコレート関数の適用がより便利になっています。ただし、シンタックスシュガーを使用するには要件があり、つまり、デコレート関数は関数オブジェクトを返す必要があります。したがって、通常、デコレートする関数を内部関数でラップし、この内部関数を返します。
次のコードを例にとります。デコレータuse_logging
は、最初にこの関数を実行し、次にデコレートされた関数bar
を返すのと同じです。したがって、bar()
が呼び出されると、実際には2つの関数を実行するのと同じであり、use_logging(bar)()
を直接呼び出すのと同じです。
def use_logging(func): def _deco(): print("%s is running" % func.__name__) func() return _deco @use_logging def bar(): print('i am bar') bar()
(II) パラメータ付き関数のデコレート
関数が2つのパラメータを受け取り、計算を実行する必要がある場合、渡された2つのパラメータa
とb
を受け取るように内部関数に対応する変更を加える必要があります。このとき、bar(1, 2)
を呼び出すことは、use_logging(bar)(1, 2)
を呼び出すことと同じです。サンプルコードは次のとおりです。
def use_logging(func): def _deco(a, b): print("%s is running" % func.__name__) func(a, b) return _deco @use_logging def bar(a, b): print('i am bar:%s' % (a + b)) bar(1, 2)
ただし、実際のアプリケーションでは、デコレートする関数のパラメータの数と型が異なる場合があります。毎回異なるパラメータ状況に合わせてデコレータを変更するのは明らかに科学的ではありません。このパラメータの問題を解決するために、Pythonの可変長パラメータ*args
と**kwargs
を使用できます。
(III) 不確定な数の関数パラメータ
以下は、パラメータのないデコレータのバージョンであり、この形式はパラメータのない関数のデコレートに適しています。*args
と**kwargs
を使用すると、デコレータはすでにさまざまな長さと型のパラメータに適応できます。つまり、このバージョンのデコレータは、あらゆる型のパラメータフリー関数をデコレートできます。サンプルコードは次のとおりです。
def use_logging(func): def _deco(*args, **kwargs): print("%s is running" % func.__name__) func(*args, **kwargs) return _deco @use_logging def bar(a, b): print('i am bar:%s' % (a + b)) @use_logging def foo(a, b, c): print('i am bar:%s' % (a + b + c)) bar(1, 2) foo(1, 2, 3)
(IV) パラメータ付きデコレータ
場合によっては、デコレータにパラメータを受け取らせる必要があります。これには、デコレータを返す高階関数を記述する必要があります。これは、実装が比較的複雑です。例:
#! /usr/bin/env python def use_logging(level): def _deco(func): def __deco(*args, **kwargs): if level == "warn": print "%s is running" % func.__name__ return func(*args, **kwargs) return __deco return _deco @use_logging(level="warn") def bar(a, b): print('i am bar:%s' % (a + b)) bar(1, 3) # Equivalent to use_logging(level="warn")(bar)(1, 3)
(V) functools.wraps
デコレータを使用すると、コードが大幅に再利用されますが、元の関数のメタ情報が失われるという欠点があります。たとえば、関数のdocstring
、__name__
、パラメータリストなどの情報です。まず、次の例を見てください。
def use_logging(func): def _deco(*args, **kwargs): print("%s is running" % func.__name__) func(*args, **kwargs) return _deco @use_logging def bar(): print('i am bar') print(bar.__name__) bar() # The output result is: # bar is running # i am bar # _deco
関数名が元のbar
ではなく_deco
になっていることがわかります。リフレクション機能を使用すると、この状況により問題が発生します。この問題を解決するために、functools.wraps
を導入できます。functools.wraps
を使用したサンプルコードは次のとおりです。
import functools def use_logging(func): @functools.wraps(func) def _deco(*args, **kwargs): print("%s is running" % func.__name__) func(*args, **kwargs) return _deco @use_logging def bar(): print('i am bar') print(bar.__name__) bar() # The output result is: # bar is running # i am bar # bar
上記の結果からわかるように、functools.wraps
を使用した後、期待される結果が得られ、元の関数の名前を正常に保持できます。
(VI) パラメータの有無にかかわらずデコレータの適応性を実現する
import functools def use_logging(arg): if callable(arg): # 渡されたパラメータが関数かどうかを判断します。パラメータのないデコレータは、このブランチを呼び出します。 @functools.wraps(arg) def _deco(*args, **kwargs): print("%s is running" % arg.__name__) arg(*args, **kwargs) return _deco else: # パラメータ付きデコレータは、このブランチを呼び出します。 def _deco(func): @functools.wraps(func) def __deco(*args, **kwargs): if arg == "warn": print "warn%s is running" % func.__name__ return func(*args, **kwargs) return __deco return _deco @use_logging("warn") # @use_logging def bar(): print('i am bar') print(bar.__name__) bar()
IV. クラスデコレータ
クラスデコレータを使用すると、パラメータ付きデコレータの効果を実現できるだけでなく、実装方法がよりエレガントで簡潔になります。同時に、継承を通じて柔軟に拡張できます。
(I) クラスデコレータ
class loging(object): def __init__(self, level="warn"): self.level = level def __call__(self, func): @functools.wraps(func) def _deco(*args, **kwargs): if self.level == "warn": self.notify(func) return func(*args, **kwargs) return _deco def notify(self, func): # logit only logs and does nothing else print "%s is running" % func.__name__ @loging(level="warn") # Execute the __call__ method def bar(a, b): print('i am bar:%s' % (a + b)) bar(1, 3)
(II) クラスデコレータの継承と拡張
class email_loging(Loging): ''' An implementation version of loging that can send an email to the administrator when the function is called ''' def __init__(self, email='admin@myproject.com', *args, **kwargs): self.email = email super(email_loging, self).__init__(*args, **kwargs) def notify(self, func): # Send an email to self.email print "%s is running" % func.__name__ print "sending email to %s" % self.email @email_loging(level="warn") def bar(a, b): print('i am bar:%s' % (a + b)) bar(1, 3)
上記のコードでは、email_loging
クラスはLoging
クラスから継承されます。この継承関係を通じて、元のクラスのコアロジックを変更することなく、関数が呼び出されたときに管理者にメールを送信するなど、新しい機能を追加できます。これは、コードの拡張と再利用におけるクラスデコレータの利点を完全に反映しています。
Leapcell: Pythonアプリのホスティングのための次世代サーバーレスプラットフォーム
最後に、Pythonサービスをデプロイするための最適なプラットフォームをお勧めします。Leapcell
1. 多言語サポート
- JavaScript、Python、Go、またはRustで開発。
2. 無制限のプロジェクトを無料でデプロイ
- 使用量のみを支払う—リクエストも料金もありません。
3. 比類のないコスト効率
- アイドル料金なしの従量課金。
- 例:$25は、平均応答時間60msで694万のリクエストをサポートします。
4. 合理化された開発者エクスペリエンス
- 簡単なセットアップのための直感的なUI。
- 完全に自動化されたCI/CDパイプラインとGitOps統合。
- 実用的な洞察を得るためのリアルタイムメトリックとロギング。
5. 容易なスケーラビリティと高パフォーマンス
- 高い並行処理を容易に処理するための自動スケーリング。
- 運用上のオーバーヘッドはゼロ—構築に集中するだけです。
Leapcell Twitter: https://x.com/LeapcellHQ