클래스 데코레이터
Coddy Python 여정의 Object Oriented Programming 섹션에 포함된 레슨 — 64개 중 40번째.
클래스 데코레이터를 사용하면 클래스를 다른 함수로 감싸서 클래스를 수정하거나 향상시킬 수 있습니다. 클래스 데코레이터는 함수 데코레이터와 유사하게 작동하지만 클래스 전체에 적용됩니다.
다음은 데코레이션이 없는 간단한 클래스입니다:
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가 작별 인사를 합니다또한 기존 메서드를 래핑(wrap)할 수도 있습니다. 원본을 저장하여 래퍼 내부에서 여전히 호출할 수 있도록 하는 방식입니다. 이는 클래스가 이미 가지고 있는 메서드에 동작(추적이나 로깅 등)을 추가하고 싶을 때 유용합니다:
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):
# 클래스 수정
return cls
@decorator_name
class MyClass:
pass예제 - 클래스에 메서드 추가하기:
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()) # Hello, my name is Alice
print(person.farewell()) # Goodbye from Alice예제 - 기존 메서드 감싸기 (원본 저장 후 교체):
def loud_greet(cls):
original_greet = cls.greet # 원본 메서드 저장
def new_greet(self):
result = original_greet(self) # 원본 호출
return result.upper() # 결과 향상
cls.greet = new_greet # 감싸진 버전으로 교체
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}이 출력되어야 함이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.