クラスデコレータ
CoddyのPythonジャーニー「Object Oriented Programming」セクションの一部 — レッスン 40/64。
クラスデコレータを使用すると、クラスを別の関数でラップすることで、クラスを変更したり拡張したりすることができます。これらは関数デコレータと同様に動作しますが、クラス全体に適用されます。
以下は、デコレーションのないシンプルなクラスです:
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デコレータをクラスに適用するには、@ を使用します:
@add_farewell
class EnhancedPerson:
def __init__(self, name):
self.name = name
def greet(self):
return f"Hello, my name is {self.name}"これで、クラスには元のメソッドと追加されたメソッドの両方が含まれるようになりました:
person = EnhancedPerson("Alice")
print(person.greet()) # こんにちは、私の名前は Alice です
print(person.farewell()) # Alice からのさようなら既存のメソッドをラップすることもできます。元のメソッドを保存しておくことで、ラッパー内から引き続き呼び出すことが可能です。これは、クラスが既に持っているメソッドに(トラッキングやロギングなどの)動作を追加したい場合に便利です。
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 が呼び出されました
# Hello, my name is Alice既存のメソッドをラップするための主な手順は次のとおりです:
1. 元のメソッドを保存します: original = cls.method
2. original(self) と追加のロジックを呼び出す新しい関数を定義します。
3. 新しい関数をクラスに再代入します: cls.method = new_function
4. 変更されたクラスを返します。
重要なポイント: クラスデコレータは、クラスを入力として受け取り、それを修正または拡張して、修正されたクラスを返します。これらはメソッドの追加、属性の変更、または既存の機能のラップを行うことができます。これらを適用するには、クラス定義の上に @decorator_name を使用します。これにより、継承を使わずにクラスの機能を拡張するクリーンな方法が提供されます。
チャレンジ
中級このチャレンジでは、メソッドの呼び出し追跡機能を追加するクラスデコレータを実装します。
decorator.py-add_counterクラスデコレータを実装しますcalculator.py- デコレータを使用するクラスが含まれています
driver.py ファイルには、さまざまなユースケース、継承パターン、およびエッジケースに対して実装を検証するための広範なテストシナリオが含まれています。このファイルを変更する必要はありませんが、内容を確認することで期待される動作を理解するのに役立ちます。
チートシート
クラスデコレータは、クラスを別の関数でラップすることで、クラスを修正または拡張します。これらはクラスを入力として受け取り、それを修正して、修正されたクラスを返します。
基本的なクラスデコレータの構文:
def decorator_name(cls):
# Modify the class
return cls
@decorator_name
class MyClass:
pass例 - クラスにメソッドを追加する:
def add_farewell(cls):
def farewell(self):
return f"Goodbye from {self.name}"
cls.farewell = farewell # Add the method to the class
return cls
@add_farewell
class EnhancedPerson:
def __init__(self, name):
self.name = name
def greet(self):
return f"Hello, my name is {self.name}"
person = EnhancedPerson("Alice")
print(person.greet()) # Hello, my name is Alice
print(person.farewell()) # Goodbye from Alice例 - 既存のメソッドをラップする(オリジナルを保存してから置き換える):
def loud_greet(cls):
original_greet = cls.greet # Store the original method
def new_greet(self):
result = original_greet(self) # Call the original
return result.upper() # Enhance the result
cls.greet = new_greet # Replace with the wrapped version
return cls
@loud_greet
class EnhancedPerson:
def __init__(self, name):
self.name = name
def greet(self):
return f"Hello, my name is {self.name}"
person = EnhancedPerson("Alice")
print(person.greet()) # HELLO, MY NAME IS ALICE既存のメソッドをラップするための重要なステップ:
original = cls.method_name — オリジナルのメソッドを保存するdef new_method(self): original(self) — ラッパーの中でそれを呼び出すcls.method_name = new_method — クラスのメソッドを置き換えるクラスデコレータを使用すると、継承を使わずにメソッドの追加、属性の修正、または既存の機能のラップを行うことができます。
自分で試してみよう
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) # {'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) # {'add': 2, 'subtract': 1}を出力するはずです
print(calc2.call_counts) # {'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) # {'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) # {'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) # {'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) # {'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) # {'add': 1, 'subtract': 1}を出力するはずです
elif test_case == "counter_reset":
calc = Calculator()
print(calc.add(5, 3)) # 8を出力するはずです
print(calc.call_counts) # {'add': 1, 'subtract': 0}を出力するはずです
# カウンターをリセット
calc.call_counts = {"add": 0, "subtract": 0}
print(calc.add(2, 7)) # 9を出力するはずです
print(calc.call_counts) # {'add': 0, 'subtract': 0}を出力するはずです
elif test_case == "counter_manipulation":
calc = Calculator()
print(calc.add(5, 3)) # 8を出力するはずです
print(calc.call_counts) # {'add': 1, 'subtract': 0}を出力するはずです
# カウンターを直接操作
calc.call_counts["add"] = 100
print(calc.add(2, 7)) # 9を出力するはずです
print(calc.call_counts) # {'add': 101, 'subtract': 0}を出力するはずです
elif test_case == "performance_test":
calc = Calculator()
# 1000回の加算操作を実行
for i in range(1000):
calc.add(i, i+1)
# 500回の減算操作を実行
for i in range(500):
calc.subtract(i+10, i)
print(calc.call_counts) # {'add': 1000, 'subtract': 500}を出力するはずですこのレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。