Menu
Coddy logo textTech

クラスデコレータ

CoddyのPythonジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 40/64。

class デコレータを使用すると、class を別の関数でラップすることで、class を変更または拡張できます。これらは関数デコレータと同様に機能しますが、class 全体に適用されます。

これは装飾のない単純なクラスです:

class Person:
    def __init__(self, name):
        self.name = name
        
    def greet(self):
        return f"Hello, my name is {self.name}"

新しいメソッドを追加するクラスデコレーターを作成します。

def add_farewell(cls):
    def farewell(self):
        return f"Goodbye from {self.name}"
    
    cls.farewell = farewell  # クラスにメソッドを追加する
    return cls

@を使用してdecoratorをclassに適用します:

@add_farewell
class EnhancedPerson:
    def __init__(self, name):
        self.name = name
        
    def greet(self):
        return f"Hello, my name is {self.name}"

これでクラスにはoriginalメソッドと追加されたメソッドの両方があります:

person = EnhancedPerson("Alice")
print(person.greet())     # こんにちは、私の名前はAliceです
print(person.farewell())  # Aliceからのさようなら

既存のmethodをwrapすることもできます。wrapper内で引き続き呼び出せるように、originalを保存します。これは、classがすでに持っているmethodの周囲にbehaviour(トラッキングやロギングなど)を追加したい場合に便利です。

def add_tracking(cls):
    original_greet = cls.greet  # 元のメソッドを保存する

    def tracked_greet(self):
        print(f"greet was called")   # 前に追加の振る舞い
        return original_greet(self)  # 元のメソッドを呼び出す

    cls.greet = tracked_greet  # クラス上のメソッドを置き換える
    return cls

@add_tracking
class TrackedPerson:
    def __init__(self, name):
        self.name = name

    def greet(self):
        return f"Hello, my name is {self.name}"

ここでgreetを呼び出すと、最初にラッパーが実行され、その後で元の処理に委譲します。

person = TrackedPerson("Alice")
print(person.greet())
# greet が呼び出されました
# こんにちは、私の名前は Alice です

既存のメソッドをラップするための主な手順は次のとおりです。
1. 元のメソッドを保存する:original = cls.method
2. original(self)を呼び出し、さらに必要なロジックを追加する新しい関数を定義する。
3. 新しい関数をクラスに戻して割り当てる:cls.method = new_function
4. 変更されたクラスを返す。

重要なポイント: class decorator は class を input として受け取り、それを変更または拡張し、変更された class を return します。method を add したり、属性を変更したり、既存の functionality をラップしたりできます。class definition の上に @decorator_name を記述して適用します。これにより、継承を使わずに class の functionality を拡張するための、すっきりした方法が提供されます。

challenge icon

チャレンジ

中級

このチャレンジでは、メソッド呼び出しの追跡機能を追加するクラスデコレーターを実装します。

  • decorator.py - add_counter クラスデコレーターを実装します
  • calculator.py - デコレーターを使用するクラスが含まれています

driver.py ファイルには、さまざまなユースケース、継承パターン、エッジケースに対して実装を検証する広範なテストシナリオが含まれています。このファイルを変更する必要はありませんが、確認することで期待される動作を理解しやすくなります。

自分で試してみよう

from calculator import Calculator

# 包括的なテストケースハンドラー
test_case = input()

if test_case == "basic_functionality":
    calc = Calculator()
    print(calc.add(5, 3))  # 8を出力するはず
    print(calc.add(2, 7))  # 9を出力するはず
    print(calc.subtract(10, 4))  # 6を出力するはず
    print(calc.call_counts)  # Should print {'add': 2, 'subtract': 1}

elif test_case == "multiple_instances":
    calc1 = Calculator()
    calc2 = Calculator()
    
    print(calc1.add(5, 3))  # 8を出力するはず
    print(calc2.add(2, 7))  # 9を出力するはず
    print(calc1.subtract(10, 4))  # 6を出力するはず
    
    # 両方のインスタンスは同じcall_countsを共有するはず
    print(calc1.call_counts)  # Should print {'add': 2, 'subtract': 1}
    print(calc2.call_counts)  # Should print {'add': 2, 'subtract': 1}
    print(calc1.call_counts is calc2.call_counts)  # Trueを出力するはず

elif test_case == "method_call_order":
    calc = Calculator()
    
    print(calc.add(1, 2))  # 3を出力するはず
    print(calc.subtract(5, 2))  # 3を出力するはず
    print(calc.add(10, 20))  # 30を出力するはず
    print(calc.add(7, 3))  # 10を出力するはず
    print(calc.call_counts)  # Should print {'add': 3, 'subtract': 1}

elif test_case == "large_values":
    calc = Calculator()
    
    print(calc.add(1000000, 2000000))  # 3000000を出力するはず
    print(calc.subtract(5000000, 2000000))  # 3000000を出力するはず
    print(calc.call_counts)  # Should print {'add': 1, 'subtract': 1}

elif test_case == "negative_values":
    calc = Calculator()
    
    print(calc.add(-5, -3))  # -8を出力するはず
    print(calc.subtract(-10, -4))  # -6を出力するはず
    print(calc.call_counts)  # Should print {'add': 1, 'subtract': 1}

elif test_case == "float_values":
    calc = Calculator()
    
    print(calc.add(5.5, 3.2))  # 8.7を出力するはず
    print(calc.subtract(10.5, 4.2))  # 6.3を出力するはず
    print(calc.call_counts)  # Should print {'add': 1, 'subtract': 1}

elif test_case == "zero_values":
    calc = Calculator()
    
    print(calc.add(0, 0))  # 0を出力するはず
    print(calc.subtract(0, 0))  # 0を出力するはず
    print(calc.call_counts)  # Should print {'add': 1, 'subtract': 1}

elif test_case == "counter_reset":
    calc = Calculator()
    
    print(calc.add(5, 3))  # 8を出力するはず
    print(calc.call_counts)  # Should print {'add': 1, 'subtract': 0}
    
    # カウンターをリセットする
    calc.call_counts = {"add": 0, "subtract": 0}
    
    print(calc.add(2, 7))  # 9を出力するはず
    print(calc.call_counts)  # Should print {'add': 0, 'subtract': 0}

elif test_case == "counter_manipulation":
    calc = Calculator()
    
    print(calc.add(5, 3))  # 8を出力するはず
    print(calc.call_counts)  # Should print {'add': 1, 'subtract': 0}
    
    # カウンターを直接操作する
    calc.call_counts["add"] = 100
    
    print(calc.add(2, 7))  # 9を出力するはず
    print(calc.call_counts)  # Should print {'add': 101, 'subtract': 0}

elif test_case == "performance_test":
    calc = Calculator()
    
    # 1000回のadd操作を実行する
    for i in range(1000):
        calc.add(i, i+1)
    
    # 500回のsubtract操作を実行する
    for i in range(500):
        calc.subtract(i+10, i)
    
    print(calc.call_counts)  # Should print {'add': 1000, 'subtract': 500}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Pythonオンラインコンパイラ