Menu
Coddy logo textTech

클래스 데코레이터

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을 사용하여 적용합니다. 이는 상속 없이 클래스 기능을 확장할 수 있는 깔끔한 방법을 제공합니다.

challenge icon

챌린지

중급

이 챌린지에서는 메서드 호출 추적 기능을 추가하는 클래스 데코레이터를 구현합니다.

  • 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}이 출력되어야 함
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

Object Oriented Programming의 모든 레슨