ホームページ > バックエンド開発 > Python チュートリアル > Python デコレータ: 包括的なガイド

Python デコレータ: 包括的なガイド

Barbara Streisand
リリース: 2025-01-05 22:53:43
オリジナル
795 人が閲覧しました

Python Decorators: A Comprehensive Guide

私が Python でプログラミングを始めたとき、確かバージョンは 3.3 でした。したがって、私がプログラミングを始めたとき、デコレーターは長い間 Python コミュニティで利用可能でした。

関数デコレータはバージョン 2.2 で Python に導入され、クラス デコレータはバージョン 2.6 で Python に導入されました。

個人的には、Python の Decorator 機能はこの言語の非常に強力な機能であると考えています。

実際、私の目標は、Python で最も理解しにくいトピックについて一連の記事を作成することです。 10 個以上あるこれらのトピックを 1 つずつ取り上げる予定です。

この記事では、デコレータのトピックのあらゆる部分についてできる限り触れていきます。

1. 歴史的背景

  • 初期 (Python 2.2 以前): デコレーターが登場する前は、関数やクラスの変更には手動のラッピングやモンキーパッチが必要になることが多く、煩雑で読みにくくなっていました。
  • メタクラス (Python 2.2): メタクラスはクラスの作成を制御する方法を提供し、後にデコレーターが提供する機能の一部を提供しますが、単純な変更には複雑でした。
  • PEP 318 (Python 2.4): デコレーターは、PEP 318 を通じて Python 2.4 に正式に導入されました。この提案は Java のアノテーションからインスピレーションを得たもので、関数やメソッドを変更するためのよりクリーンでより宣言的な方法を提供することを目的としています。 .
  • クラス デコレーター (Python 2.6): Python 2.6 では、デコレーターのサポートがクラスに拡張され、クラスの汎用性がさらに強化されました。
  • 広範な採用: デコレータはすぐに人気の機能となり、Flask や Django などのフレームワークでルーティングや認証などに広く使用されています。

2. デコレータとは何ですか?

本質的に、デコレータは Python の設計パターンであり、これを使用すると、コア構造を変更せずに関数またはクラスの動作を変更できます。デコレーターはメタプログラミングの一種であり、基本的に他のコードを操作するコードを作成します。

Python は、以下の順序で指定されたスコープを使用して名前を解決することをご存知でしょう:

  1. ローカル
  2. 同封
  3. グローバル
  4. 内蔵

デコレータは、クロージャの概念と密接に関連するエンクロージング スコープに座っています。

重要なアイデア: デコレーターは関数を入力として受け取り、それに機能を追加し、変更された関数を返します。

例え: デコレーターをギフトの包装紙と考えてください。ギフト (本来の機能) があり、それを装飾紙 (デコレーター) で包み、見た目を良くしたり、追加の機能 (リボンやカードなど) を追加したりします。中身のギフトは変わりませんが、そのプレゼンテーションや関連するアクションが強化されています。

A) デコレータのバリエーション: 関数ベースとクラスベース

Python のほとんどのデコレータは関数を使用して実装されますが、クラスを使用してデコレータを作成することもできます。
関数ベースのデコレータはより一般的でシンプルですが、クラスベースのデコレータはさらなる柔軟性を提供します。

関数ベースの基本的なデコレータ構文

def my_decorator(func):
    def wrapper(*args, **kwargs):
        # Do something before calling the decorated function
        print("Before function call")
        result = func(*args, **kwargs)
        # Do something after calling the decorated function
        print("After function call")
        return result
    return wrapper

@my_decorator
def say_hello(name):
    print(f"Hello, {name}!")

say_hello("World")
ログイン後にコピー
ログイン後にコピー

説明:

  • my_decorator はデコレータ関数です。装飾される関数 func を入力として受け取ります。
  • Wrapper は、元の関数の呼び出しをラップする内部関数です。元の関数の前後でコードを実行できます。
  • @my_decorator はデコレータ構文です。これは、say_hello = my_decorator(say_hello) と同等です。

クラスベースの基本的なデコレータ構文

これらは、関数の代わりにクラスを使用してデコレーターを定義します。

class MyDecorator:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        # Do something before calling the decorated function
        print("Before function call")
        result = self.func(*args, **kwargs)
        # Do something after calling the decorated function
        print("After function call")
        return result

@MyDecorator
def say_hello(name):
    print(f"Hello, {name}!")

say_hello("World")
ログイン後にコピー
ログイン後にコピー

説明:

  • MyDecorator は、デコレーターとして機能するクラスです。
  • __init__ メソッドには、装飾される関数が格納されます。
  • __call__ メソッドはクラス インスタンスを呼び出し可能にし、関数のように使用できるようにします。

B) 単純なデコレータの実装

デコレーターの基本概念は、別の関数を引数として受け取り、明示的に変更することなくその動作を拡張する関数であるということです。
最も単純な形式は次のとおりです:

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

# Using the decorator with @ syntax
@my_decorator
def say_hello():
    print("Hello!")

# When we call say_hello()
say_hello()

# This is equivalent to:
# say_hello = my_decorator(say_hello)
ログイン後にコピー
ログイン後にコピー

C) 引数を使用したデコレータの実装

関数の実行時間を記録するデコレータを作成しましょう:

def decorator_with_args(func):
    def wrapper(*args, **kwargs):    # Accept any number of arguments
        print(f"Arguments received: {args}, {kwargs}")
        return func(*args, **kwargs)  # Pass arguments to the original function
    return wrapper

@decorator_with_args
def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}!")

greet("Alice", greeting="Hi")  # Prints arguments then "Hi, Alice!"
ログイン後にコピー
ログイン後にコピー

D) パラメータ化されたデコレータの実装

これらは独自のパラメータを受け入れることができるデコレータです:

def repeat(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(times=3)
def greet(name):
    print(f"Hello {name}")
    return "Done"

greet("Bob")  # Prints "Hello Bob" three times
ログイン後にコピー

E) クラスデコレータの実装

def singleton(cls):
    instances = {}
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return get_instance

@singleton
class DatabaseConnection:
    def __init__(self):
        print("Initializing database connection")

# Creating multiple instances actually returns the same instance
db1 = DatabaseConnection()  # Prints initialization
db2 = DatabaseConnection()  # No initialization printed
print(db1 is db2)  # True
ログイン後にコピー

F) メソッドデコレータの実装

これらは、クラス メソッド用に特別に設計されています:

def debug_method(func):
    def wrapper(self, *args, **kwargs):
        print(f"Calling method {func.__name__} of {self.__class__.__name__}")
        return func(self, *args, **kwargs)
    return wrapper

class MyClass:
    @debug_method
    def my_method(self, x, y):
        return x + y

obj = MyClass()
print(obj.my_method(5, 3))

ログイン後にコピー

G) デコレータチェーンの実装

複数のデコレータを 1 つの関数に適用できます:

def bold(func):
    def wrapper():
        return "<b>" + func() + "</b>"
    return wrapper

def italic(func):
    def wrapper():
        return "<i>" + func() + "</i>"
    return wrapper

@bold
@italic
def greet():
    return "Hello!"

print(greet())  # Outputs: <b><i>Hello!</i></b>
ログイン後にコピー

説明:

  • デコレータは下から上に適用されます。
  • これは、数学で行うこと、f(g(x)) に似ています。
  • 斜体が最初に適用され、次に太字が適用されます。

H) @functools.wraps を使用しない場合はどうなりますか?

functools.wraps デコレータ (ドキュメントを参照) は、デコレータでラップしたときに元の関数のメタデータ (名前、docstring、シグネチャなど) を保持するヘルパー関数です。使用しないと、この重要な情報が失われます。

例:

def my_decorator(func):
    def wrapper(*args, **kwargs):
        """Wrapper docstring"""
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def my_function():
    """My function docstring"""
    pass

print(my_function.__name__)
print(my_function.__doc__)
ログイン後にコピー

出力:

wrapper
Wrapper docstring
ログイン後にコピー

問題:

  • 元の関数の名前 (my_function) と docstring ("My function docstring") は失われます。
  • これにより、デバッグとイントロスペクションが困難になる可能性があります。

解決策: functools.wraps を使用します):

def my_decorator(func):
    def wrapper(*args, **kwargs):
        # Do something before calling the decorated function
        print("Before function call")
        result = func(*args, **kwargs)
        # Do something after calling the decorated function
        print("After function call")
        return result
    return wrapper

@my_decorator
def say_hello(name):
    print(f"Hello, {name}!")

say_hello("World")
ログイン後にコピー
ログイン後にコピー

出力:

class MyDecorator:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        # Do something before calling the decorated function
        print("Before function call")
        result = self.func(*args, **kwargs)
        # Do something after calling the decorated function
        print("After function call")
        return result

@MyDecorator
def say_hello(name):
    print(f"Hello, {name}!")

say_hello("World")
ログイン後にコピー
ログイン後にコピー

functools.wraps の利点:

  • 関数のメタデータを保持します。
  • コードの可読性と保守性が向上します。
  • デバッグが容易になります。
  • イントロスペクション ツールとドキュメント ジェネレーターに役立ちます。

I) 状態を持つデコレータ

デコレータは関数呼び出し間の状態を維持することもできます。これは、関数呼び出しのキャッシュやカウントなどのシナリオで特に役立ちます。

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

# Using the decorator with @ syntax
@my_decorator
def say_hello():
    print("Hello!")

# When we call say_hello()
say_hello()

# This is equivalent to:
# say_hello = my_decorator(say_hello)
ログイン後にコピー
ログイン後にコピー

出力:

def decorator_with_args(func):
    def wrapper(*args, **kwargs):    # Accept any number of arguments
        print(f"Arguments received: {args}, {kwargs}")
        return func(*args, **kwargs)  # Pass arguments to the original function
    return wrapper

@decorator_with_args
def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}!")

greet("Alice", greeting="Hi")  # Prints arguments then "Hi, Alice!"
ログイン後にコピー
ログイン後にコピー

説明:

ラッパー関数は、装飾された関数が呼び出されるたびに増加するカウンター (呼び出し) を維持します。
これは、デコレータを使用して状態を維持する方法の簡単な例です。

J) Python デコレータのベスト プラクティス

  • functools.wraps を使用する: 元の関数のメタデータを保持するには、デコレーターで常に @functools.wraps を使用します。
  • デコレータをシンプルに保つ: デコレータは、理想的には 1 つの特定のことを、それをうまく実行する必要があります。これにより、再利用可能になり、理解しやすくなります。
  • デコレータを文書化します: デコレータが何をするのか、どのような引数を受け取り、何を返すのかを説明します。
  • デコレータをテストする: 単体テストを作成して、さまざまなシナリオでデコレータが期待どおりに動作することを確認します。
  • チェーンの順序を考慮する: 複数のデコレータをチェーンするときは、実行フローに影響するため、順序に注意してください。

K) 不適切な実装 (アンチパターン)

  • 過度に複雑なデコレータ: 複雑すぎるデコレータを作成したり、多くのことを実行しようとしたりするデコレータを作成しないでください。そのため、理解、保守、デバッグが困難になります。
  • functools.wraps の無視: @functools.wraps の使用を忘れると、関数のメタデータが失われ、イントロスペクションやデバッグで問題が発生する可能性があります。
  • 副作用: デコレーターには、装飾された関数の変更以外に意図しない副作用が発生しないことが理想的です。
  • 値のハードコーディング: デコレーター内の値のハードコーディングは避けてください。代わりに、デコレータ ファクトリを使用して設定可能にします。
  • 引数が適切に処理されない: デコレータがさまざまな関数で使用されることを意図している場合は、ラッパー関数が *args と **kwargs を使用して任意の数の位置引数とキーワード引数を処理できることを確認してください。

L) 10. 現実世界の使用例

  • ログ: デバッグまたは監査のために関数呼び出し、引数、戻り値を記録します。
  • タイミング: パフォーマンス分析のための関数の実行時間を測定します。
  • キャッシュ: 冗長な計算 (メモ化) を避けるために、高コストの関数呼び出しの結果を保存します。
  • 認証と認可: 関数を実行する前にユーザーの資格情報または権限を確認します。
  • 入力検証: 関数に渡された引数が特定の基準を満たしているかどうかを確認します。
  • レート制限: 特定の期間内に関数を呼び出すことができる回数を制御します。
  • 再試行ロジック: 一時的なエラーにより関数呼び出しが失敗した場合、関数呼び出しを自動的に再試行します。
  • フレームワーク固有のタスク: Flask や Django などのフレームワークは、ルーティング (URL を関数にマッピング)、プラグインの登録などにデコレーターを使用します。

M) Python デコレータの厳選されたリスト

Python デコレータの厳選されたリストは以下で見つけることができます:

  • 素晴らしい Python デコレータ
  • Python デコレータ ライブラリ

N) 11. 結論

デコレーターは、Python の強力かつエレガントな機能であり、関数やクラスをクリーンで宣言的な方法で強化できます。
原則、ベスト プラクティス、潜在的な落とし穴を理解することで、デコレータを効果的に活用して、よりモジュール化され、保守しやすく、表現力豊かなコードを作成できます。
これらは、Python プログラマーにとって貴重なツールであり、特にフレームワークを使用したり、再利用可能なコンポーネントを構築したりする場合に役立ちます。

以上がPython デコレータ: 包括的なガイドの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート