デコレータは Python の非常に強力な機能で、関数の動作や機能をより柔軟に変更できるようになります。
すべての Python 開発者の最初の目標は、コードを動作させることです。徐々に、読みやすさとスケーラビリティについて心配するようになりました。この時点で、デコレータについて考え始めます。
デコレータは、関数に特別な動作を追加する優れた方法です。また、データ サイエンティストが関数定義に注入する必要があるものはほとんどありません。
デコレータを使用すると、コードの重複がどれだけ減り、読みやすさが向上するか驚くでしょう。もちろん私もそうです。
ここでは、ほぼすべてのデータ プロジェクトで私が使用する最も一般的なツールを 5 つ紹介します。
技術交流
テクノロジーは共有とコミュニケーションを学ぶ必要があり、密室で作業することはお勧めできません。1 人で速く進むこともできますし、グループでより遠くまで進むこともできます。
良い記事は、ファンの共有と推奨、ドライデータ、データ共有、データ、技術交流の向上と切り離せないものであり、これらはすべてコミュニケーショングループを追加することで取得できます。グループには2,000人以上の友達がいます。グループを追加する最良の方法メモは、情報源 + 興味のある方向性を備えており、同じ考えを持つ友人を簡単に見つけることができます。
方法①、WeChatアカウントを追加: dkl88191、備考: CSDN + Pythonから
方法②、WeChat検索公式アカウント: Python学習とデータマイニング、バックグラウンド返信: グループを追加
1. リトライデコレータ
データ サイエンスやソフトウェア開発プロジェクトでは、外部システムに依存することがよくあります。しかし、すべてをコントロールできるわけではありません。
予期せぬ事態が発生した場合、外部システムが自動的に修正して再実行するまで、コードを一定時間待機させたい場合があります。
私はこの再試行ロジックを Python デコレーターで実装することを好みます。そうすれば、任意の関数に注釈を付けて再試行動作を適用できます。
以下は再試行デコレータのコードです。
import time
from functools import wraps
def retry(max_tries=3, delay_seconds=1):
def decorator_retry(func):
@wraps(func)
def wrapper_retry(*args, **kwargs):
tries = 0
while tries < max_tries:
try:
return func(*args, **kwargs)
except Exception as e:
tries += 1
if tries == max_tries:
raise e
time.sleep(delay_seconds)
return wrapper_retry
return decorator_retry
@retry(max_tries=5, delay_seconds=2)
def call_dummy_api():
response = requests.get("https://jsonplaceholder.typicode.com/todos/1")
return response
上記のコードでは、API 応答を取得しようとしています。失敗した場合は、同じタスクを 5 回再試行します。各再試行の間に 2 秒待機します。
2. キャッシュ機能の結果
コードベースの特定の部分の動作はほとんど変更されません。ただし、計算負荷が高くなる可能性があります。この場合、デコレータを使用して関数呼び出しをキャッシュできます。
入力が同じ場合、関数は 1 回だけ実行されます。後続の実行ごとに、結果がキャッシュからフェッチされます。したがって、コストのかかる計算を常に実行する必要はありません。
def memoize(func):
cache = {
}
def wrapper(*args):
if args in cache:
return cache[args]
else:
result = func(*args)
cache[args] = result
return result
return wrapper
デコレータは辞書を使用して関数の引数と戻り値を保存します。この関数を実行すると、デコレータは以前の結果辞書をチェックします。実際の関数は、値が格納されていない場合にのみ呼び出されます。
以下はフィボナッチ数の計算関数です。これは再帰関数であるため、同じ関数が複数回実行されます。しかし、キャッシュを使用すると、プロセスを高速化できます。
@memoize
def fibonacci(n):
if n <= 1:
return n
else:
return fibonacci(n-1) + fibonacci(n-2)
この関数のキャッシュありとなしの実行時間は以下のとおりです。キャッシュされたバージョンの実行にはわずか数ミリ秒しかかかりませんが、キャッシュされていないバージョンの実行には 1 分近くかかることに注意してください。
Function slow_fibonacci took 53.05560088157654 seconds to run.
Function fast_fibonacci took 7.772445678710938e-05 seconds to run.
辞書を使用して以前の実行データを保持するのは簡単な方法です。ただし、キャッシュされたデータを保存するより洗練された方法があります。Redis のようなインメモリ データベースを使用できます。
3. タイミング機能
これは驚くことではありません。データ集約型の関数を扱うときは、実行にどれくらいの時間がかかるかを知りたいと考えます。
一般的には、関数の開始時と終了時に 1 つずつ、計 2 つのタイムスタンプを収集します。その後、期間を計算し、戻り値とともに出力できます。
しかし、複数の機能に対してこれを繰り返し行うのは面倒です。
代わりに、デコレーターを使用してこの作業を行うことができます。期間を出力する必要がある関数には注釈を付けることができます。
呼び出されたときに関数のランタイムを出力する Python デコレーターの例を次に示します。
import time
def timing_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"Function {
func.__name__} took {
end_time - start_time} seconds to run.")
return result
return wrapper
このデコレータを使用して、関数の実行時間を計測できます。
@timing_decorator
def my_function():
# some code here
time.sleep(1) # simulate some time-consuming operation
return
関数を呼び出すと、実行にかかる時間が出力されます。
my_function()
>>> Function my_function took 1.0019128322601318 seconds to run.
4. 関数呼び出しを記録する
このデコレータは前のデコレータと非常によく似ています。しかし、それにはいくつかの特定の用途があります。
ソフトウェア設計原則に従う場合は、単一責任原則が理解できるでしょう。これは、各機能が担当する責任は 1 つだけである必要があることを意味します。
このようにコードを設計する場合は、関数の実行に関する情報もログに記録する必要があります。ここで、ログ デコレータが役に立ちます。
次の例はこれを示しています。
import logging
import functools
logging.basicConfig(level=logging.INFO)
def log_execution(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
logging.info(f"Executing {
func.__name__}")
result = func(*args, **kwargs)
logging.info(f"Finished executing {
func.__name__}")
return result
return wrapper
@log_execution
def extract_data(source):
# extract data from source
data = ...
return data
@log_execution
def transform_data(data):
# transform data
transformed_data = ...
return transformed_data
@log_execution
def load_data(data, target):
# load data into target
...
def main():
# extract data
data = extract_data(source)
# transform data
transformed_data = transform_data(data)
# load data
load_data(transformed_data, target)
上記のコードは、ETL パイプラインの簡略化されたバージョンです。抽出、変換、読み込みの各ステップを処理する 3 つの個別の関数があります。それぞれを log_execution デコレータでラップします。
このコードを実行すると、次のような出力が表示されます。
INFO:root:Executing extract_data
INFO:root:Finished executing extract_data
INFO:root:Executing transform_data
INFO:root:Finished executing transform_data
INFO:root:Executing load_data
INFO:root:Finished executing load_data
このデコレータで実行時間を出力することもできます。ただし、私はそれらを別々のデコレータに入れることを好みます。こうすることで、関数にどちら (または両方) を使用するかを選択できます。
ここでは、1 つの関数で複数のデコレータを使用する方法を説明します。
@log_execution
@timing_decorator
def my_function(x, y):
time.sleep(1)
return x + y
5. 通知デコレーター
最後に、運用システムで非常に役立つデコレータの 1 つが通知デコレータです。
十分にテストされたコードベースであっても、何度も再試行した後でも障害が発生する可能性があります。このようなことが起こった場合、迅速な行動を起こすように誰かに通知する必要があります。
データ パイプラインを構築し、それが永久に機能することを望んだことがあるなら、これは新しいことではありません。
次のデコレータは、内部関数の実行が失敗したときに電子メールを送信します。あなたの場合、電子メール通知である必要はありません。Teams/Slack 通知を送信するように設定できます。
import smtplib
import traceback
from email.mime.text import MIMEText
def email_on_failure(sender_email, pd, recipient_email):
def decorator(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
# format the error message and traceback
err_msg = f"Error: {
str(e)}\n\nTraceback:\n{
traceback.format_exc()}"
# create the email message
message = MIMEText(err_msg)
message['Subject'] = f"{
func.__name__} failed"
message['From'] = sender_email
message['To'] = recipient_email
# send the email
with smtplib.SMTP_SSL('smtp.gmail.com', 465) as smtp:
smtp.login(sender_email, pd)
smtp.sendmail(sender_email, recipient_email, message.as_string())
# re-raise the exception
raise
return wrapper
return decorator
@email_on_failure(sender_email='[email protected]', pd='your_pd', recipient_email='[email protected]')
def my_function():
# code that might fail
結論は
デコレータは、関数に新しい動作を適用する非常に便利な方法です。これらがなければ、多くのコードが重複することになります。
この記事では、私が最もよく使用するデコレータについて説明します。ニーズに応じてこれらを拡張できます。私はすべてのプロジェクトでこれらのデコレータの一部のバージョンを使用しています。これらは動作が若干異なりますが、共通の目標を達成するためによく使用するデコレータです。